MOBILE-4270 question: Improve base question component class
parent
51bd21163a
commit
5cb74fca86
|
@ -1,57 +1,57 @@
|
|||
<ion-list class="addon-qtype-calculated-container" *ngIf="calcQuestion && (calcQuestion.text || calcQuestion.text === '')">
|
||||
<ion-list class="addon-qtype-calculated-container" *ngIf="question && (question.text || question.text === '')">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="calcQuestion.text" [contextLevel]="contextLevel"
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- Display unit options before the answer input. -->
|
||||
<ng-container *ngIf="calcQuestion.options && calcQuestion.options.length && calcQuestion.optionsFirst">
|
||||
<ng-container *ngIf="question.options && question.options.length && question.optionsFirst">
|
||||
<ng-container *ngTemplateOutlet="radioUnits"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ion-item *ngIf="calcQuestion.input" class="ion-text-wrap core-{{calcQuestion.input.correctIconColor}}-item">
|
||||
<ion-item *ngIf="question.input" class="ion-text-wrap core-{{question.input.correctIconColor}}-item">
|
||||
<ion-label position="stacked">{{ 'addon.mod_quiz.answercolon' | translate }}</ion-label>
|
||||
|
||||
<div class="flex-row">
|
||||
<!-- Display unit select before the answer input. -->
|
||||
<ng-container *ngIf="calcQuestion.select && calcQuestion.selectFirst">
|
||||
<ng-container *ngIf="question.select && question.selectFirst">
|
||||
<ng-container *ngTemplateOutlet="selectUnits"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
<!-- Input to enter the answer. -->
|
||||
<ion-input type="text" [attr.name]="calcQuestion.input.name"
|
||||
[placeholder]="calcQuestion.input.readOnly ? '' : 'core.question.answer' | translate" [value]="calcQuestion.input.value"
|
||||
[disabled]="calcQuestion.input.readOnly" autocorrect="off">
|
||||
<ion-input type="text" [attr.name]="question.input.name"
|
||||
[placeholder]="question.input.readOnly ? '' : 'core.question.answer' | translate" [value]="question.input.value"
|
||||
[disabled]="question.input.readOnly" autocorrect="off">
|
||||
</ion-input>
|
||||
|
||||
<!-- Display unit select after the answer input. -->
|
||||
<ng-container *ngIf="calcQuestion.select && !calcQuestion.selectFirst">
|
||||
<ng-container *ngIf="question.select && !question.selectFirst">
|
||||
<ng-container *ngTemplateOutlet="selectUnits"></ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ion-icon *ngIf="calcQuestion.input.correctIcon" class="core-correct-icon ion-align-self-center" slot="end"
|
||||
[name]="calcQuestion.input.correctIcon" [color]="[calcQuestion.input.correctIconColor]">
|
||||
<ion-icon *ngIf="question.input.correctIcon" class="core-correct-icon ion-align-self-center" slot="end"
|
||||
[name]="question.input.correctIcon" [color]="[question.input.correctIconColor]">
|
||||
</ion-icon>
|
||||
</ion-item>
|
||||
|
||||
<!-- Display unit options after the answer input. -->
|
||||
<ng-container *ngIf="calcQuestion.options && calcQuestion.options.length && !calcQuestion.optionsFirst">
|
||||
<ng-container *ngIf="question.options && question.options.length && !question.optionsFirst">
|
||||
<ng-container *ngTemplateOutlet="radioUnits"></ng-container>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
|
||||
<!-- Template for units entered using a select. -->
|
||||
<ng-template #selectUnits>
|
||||
<label *ngIf="calcQuestion!.select!.accessibilityLabel" class="accesshide" for="{{calcQuestion!.select!.id}}">
|
||||
{{ calcQuestion!.select!.accessibilityLabel }}
|
||||
<label *ngIf="question!.select!.accessibilityLabel" class="accesshide" for="{{question!.select!.id}}">
|
||||
{{ question!.select!.accessibilityLabel }}
|
||||
</label>
|
||||
<ion-select id="{{calcQuestion!.select!.id}}" [name]="calcQuestion!.select!.name" [(ngModel)]="calcQuestion!.select!.selected"
|
||||
interface="action-sheet" [disabled]="calcQuestion!.select!.disabled" [slot]="calcQuestion?.selectFirst ? 'start' : 'end'"
|
||||
<ion-select id="{{question!.select!.id}}" [name]="question!.select!.name" [(ngModel)]="question!.select!.selected"
|
||||
interface="action-sheet" [disabled]="question!.select!.disabled" [slot]="question?.selectFirst ? 'start' : 'end'"
|
||||
[interfaceOptions]="{header: 'addon.mod_quiz.unit' | translate}">
|
||||
<ion-select-option *ngFor="let option of calcQuestion!.select!.options" [value]="option.value">
|
||||
<ion-select-option *ngFor="let option of question!.select!.options" [value]="option.value">
|
||||
{{option.label}}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
|
@ -59,15 +59,15 @@
|
|||
|
||||
<!-- Template for units entered using radio buttons. -->
|
||||
<ng-template #radioUnits>
|
||||
<ion-radio-group [(ngModel)]="calcQuestion!.unit" [name]="calcQuestion!.optionsName">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let option of calcQuestion!.options">
|
||||
<ion-radio-group [(ngModel)]="question!.unit" [name]="question!.optionsName">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let option of question!.options">
|
||||
<ion-label>{{ option.text }}</ion-label>
|
||||
<ion-radio slot="end" [value]="option.value" [disabled]="option.disabled || calcQuestion!.input?.readOnly"
|
||||
[color]="calcQuestion!.input?.correctIconColor">
|
||||
<ion-radio slot="end" [value]="option.value" [disabled]="option.disabled || question!.input?.readOnly"
|
||||
[color]="question!.input?.correctIconColor">
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<!-- ion-radio doesn't use an input. Create a hidden input to hold the selected value. -->
|
||||
<input type="hidden" [ngModel]="calcQuestion!.unit" [attr.name]="calcQuestion!.optionsName">
|
||||
<input type="hidden" [ngModel]="question!.unit" [attr.name]="question!.optionsName">
|
||||
</ion-radio-group>
|
||||
</ng-template>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import { Component, ElementRef } from '@angular/core';
|
||||
|
||||
import { AddonModQuizCalculatedQuestion, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
|
||||
|
@ -23,9 +23,7 @@ import { AddonModQuizCalculatedQuestion, CoreQuestionBaseComponent } from '@feat
|
|||
selector: 'addon-qtype-calculated',
|
||||
templateUrl: 'addon-qtype-calculated.html',
|
||||
})
|
||||
export class AddonQtypeCalculatedComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
calcQuestion?: AddonModQuizCalculatedQuestion;
|
||||
export class AddonQtypeCalculatedComponent extends CoreQuestionBaseComponent<AddonModQuizCalculatedQuestion> {
|
||||
|
||||
constructor(elementRef: ElementRef) {
|
||||
super('AddonQtypeCalculatedComponent', elementRef);
|
||||
|
@ -34,10 +32,8 @@ export class AddonQtypeCalculatedComponent extends CoreQuestionBaseComponent imp
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
this.initCalculatedComponent();
|
||||
|
||||
this.calcQuestion = this.question;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -708,9 +708,15 @@ export class AddonQtypeDdImageOrTextQuestionDocStructure {
|
|||
this.topNode = this.container.querySelector<HTMLElement>('.addon-qtype-ddimageortext-container');
|
||||
this.dragItemsArea = this.topNode?.querySelector<HTMLElement>('div.draghomes') || null;
|
||||
|
||||
if (!this.topNode) {
|
||||
this.logger.error('ddimageortext container not found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dragItemsArea) {
|
||||
// On 3.9+ dragitems were removed.
|
||||
const dragItems = this.topNode!.querySelector('div.dragitems');
|
||||
const dragItems = this.topNode.querySelector('div.dragitems');
|
||||
|
||||
if (dragItems) {
|
||||
// Remove empty div.dragitems.
|
||||
|
@ -718,10 +724,10 @@ export class AddonQtypeDdImageOrTextQuestionDocStructure {
|
|||
}
|
||||
|
||||
// 3.6+ site, transform HTML so it has the same structure as in Moodle 3.5.
|
||||
const ddArea = this.topNode!.querySelector('div.ddarea');
|
||||
const ddArea = this.topNode.querySelector('div.ddarea');
|
||||
if (ddArea) {
|
||||
// Move div.dropzones to div.ddarea.
|
||||
const dropZones = this.topNode!.querySelector('div.dropzones');
|
||||
const dropZones = this.topNode.querySelector('div.dropzones');
|
||||
if (dropZones) {
|
||||
ddArea.appendChild(dropZones);
|
||||
}
|
||||
|
@ -738,7 +744,7 @@ export class AddonQtypeDdImageOrTextQuestionDocStructure {
|
|||
draghome.classList.add(`dragitemhomes${index}`);
|
||||
});
|
||||
} else {
|
||||
this.dragItemsArea = this.topNode!.querySelector<HTMLElement>('div.dragitems');
|
||||
this.dragItemsArea = this.topNode.querySelector<HTMLElement>('div.dragitems');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -797,14 +803,15 @@ export class AddonQtypeDdImageOrTextQuestionDocStructure {
|
|||
getClassnameNumericSuffix(node: HTMLElement, prefix: string): number | undefined {
|
||||
if (node.classList && node.classList.length) {
|
||||
const patt1 = new RegExp(`^${prefix}([0-9])+$`);
|
||||
const patt2 = new RegExp('([0-9])+$');
|
||||
|
||||
for (let index = 0; index < node.classList.length; index++) {
|
||||
if (patt1.test(node.classList[index])) {
|
||||
const match = patt2.exec(node.classList[index]);
|
||||
const classFound = Array.from(node.classList)
|
||||
.find((className) => patt1.test(className));
|
||||
|
||||
return Number(match![0]);
|
||||
}
|
||||
if (classFound) {
|
||||
const patt2 = new RegExp('([0-9])+$');
|
||||
const match = patt2.exec(classFound);
|
||||
|
||||
return Number(match?.[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
<div *ngIf="ddQuestion && (ddQuestion.text || ddQuestion.text === '')" class="addon-qtype-ddimageortext-container">
|
||||
<div *ngIf="question && (question.text || question.text === '')" class="addon-qtype-ddimageortext-container">
|
||||
<!-- Content is outside the core-loading to let the script calculate drag items position -->
|
||||
<core-loading [hideUntil]="ddQuestion.loaded"></core-loading>
|
||||
<core-loading [hideUntil]="question.loaded"></core-loading>
|
||||
|
||||
<ion-item class="ion-text-wrap" [hidden]="!ddQuestion.loaded">
|
||||
<ion-item class="ion-text-wrap" [hidden]="!question.loaded">
|
||||
<ion-label>
|
||||
<ion-card *ngIf="!ddQuestion.readOnly" class="core-info-card">
|
||||
<ion-card *ngIf="!question.readOnly" class="core-info-card">
|
||||
<ion-item>
|
||||
<ion-icon name="fas-info-circle" slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-label>{{ 'core.question.howtodraganddrop' | translate }}</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="ddQuestion.text" [contextLevel]="contextLevel"
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId" (afterRender)="textRendered()">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<div class="fake-ion-item ion-text-wrap" [hidden]="!ddQuestion.loaded">
|
||||
<core-format-text *ngIf="ddQuestion.ddArea" [adaptImg]="false" [component]="component" [componentId]="componentId"
|
||||
[text]="ddQuestion.ddArea" [filter]="false" (afterRender)="ddAreaRendered()">
|
||||
<div class="fake-ion-item ion-text-wrap" [hidden]="!question.loaded">
|
||||
<core-format-text *ngIf="question.ddArea" [adaptImg]="false" [component]="component" [componentId]="componentId"
|
||||
[text]="question.ddArea" [filter]="false" (afterRender)="ddAreaRendered()">
|
||||
</core-format-text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -12,11 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, ElementRef } from '@angular/core';
|
||||
import { Component, OnDestroy, ElementRef } from '@angular/core';
|
||||
|
||||
import { AddonModQuizQuestionBasicData, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
import { CoreQuestionHelper } from '@features/question/services/question-helper';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { AddonQtypeDdImageOrTextQuestion } from '../classes/ddimageortext';
|
||||
|
||||
/**
|
||||
|
@ -27,9 +26,9 @@ import { AddonQtypeDdImageOrTextQuestion } from '../classes/ddimageortext';
|
|||
templateUrl: 'addon-qtype-ddimageortext.html',
|
||||
styleUrls: ['ddimageortext.scss'],
|
||||
})
|
||||
export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent implements OnInit, OnDestroy {
|
||||
|
||||
ddQuestion?: AddonModQuizDdImageOrTextQuestionData;
|
||||
export class AddonQtypeDdImageOrTextComponent
|
||||
extends CoreQuestionBaseComponent<AddonModQuizDdImageOrTextQuestionData>
|
||||
implements OnDestroy {
|
||||
|
||||
protected questionInstance?: AddonQtypeDdImageOrTextQuestion;
|
||||
protected drops?: unknown[]; // The drop zones received in the init object of the question.
|
||||
|
@ -44,50 +43,47 @@ export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
if (!this.question) {
|
||||
this.logger.warn('Aborting because of no question received.');
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
return;
|
||||
}
|
||||
|
||||
this.ddQuestion = this.question;
|
||||
|
||||
const element = CoreDomUtils.convertToElement(this.ddQuestion.html);
|
||||
const questionElement = this.initComponent();
|
||||
if (!questionElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get D&D area and question text.
|
||||
const ddArea = element.querySelector('.ddarea');
|
||||
|
||||
this.ddQuestion.text = CoreDomUtils.getContentsOfElement(element, '.qtext');
|
||||
if (!ddArea || this.ddQuestion.text === undefined) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.ddQuestion.slot);
|
||||
const ddArea = questionElement.querySelector('.ddarea');
|
||||
if (!ddArea) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.question.slot);
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
|
||||
// Set the D&D area HTML.
|
||||
this.ddQuestion.ddArea = ddArea.outerHTML;
|
||||
this.ddQuestion.readOnly = false;
|
||||
this.question.ddArea = ddArea.outerHTML;
|
||||
this.question.readOnly = false;
|
||||
|
||||
if (this.ddQuestion.initObjects) {
|
||||
if (this.question.initObjects) {
|
||||
// Moodle version = 3.5.
|
||||
if (this.ddQuestion.initObjects.drops !== undefined) {
|
||||
this.drops = <unknown[]> this.ddQuestion.initObjects.drops;
|
||||
if (this.question.initObjects.drops !== undefined) {
|
||||
this.drops = <unknown[]> this.question.initObjects.drops;
|
||||
}
|
||||
if (this.ddQuestion.initObjects.readonly !== undefined) {
|
||||
this.ddQuestion.readOnly = !!this.ddQuestion.initObjects.readonly;
|
||||
if (this.question.initObjects.readonly !== undefined) {
|
||||
this.question.readOnly = !!this.question.initObjects.readonly;
|
||||
}
|
||||
} else if (this.ddQuestion.amdArgs) {
|
||||
} else if (this.question.amdArgs) {
|
||||
// Moodle version >= 3.6.
|
||||
if (this.ddQuestion.amdArgs[1] !== undefined) {
|
||||
this.ddQuestion.readOnly = !!this.ddQuestion.amdArgs[1];
|
||||
if (this.question.amdArgs[1] !== undefined) {
|
||||
this.question.readOnly = !!this.question.amdArgs[1];
|
||||
}
|
||||
if (this.ddQuestion.amdArgs[2] !== undefined) {
|
||||
this.drops = <unknown[]> this.ddQuestion.amdArgs[2];
|
||||
if (this.question.amdArgs[2] !== undefined) {
|
||||
this.drops = <unknown[]> this.question.amdArgs[2];
|
||||
}
|
||||
}
|
||||
|
||||
this.ddQuestion.loaded = false;
|
||||
this.question.loaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,12 +110,12 @@ export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent
|
|||
* The question has been rendered.
|
||||
*/
|
||||
protected questionRendered(): void {
|
||||
if (!this.destroyed && this.ddQuestion) {
|
||||
if (!this.destroyed && this.question) {
|
||||
// Create the instance.
|
||||
this.questionInstance = new AddonQtypeDdImageOrTextQuestion(
|
||||
this.hostElement,
|
||||
this.ddQuestion,
|
||||
!!this.ddQuestion.readOnly,
|
||||
this.question,
|
||||
!!this.question.readOnly,
|
||||
this.drops,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
<div *ngIf="ddQuestion && (ddQuestion.text || ddQuestion.text === '')" class="addon-qtype-ddmarker-container">
|
||||
<div *ngIf="question && (question.text || question.text === '')" class="addon-qtype-ddmarker-container">
|
||||
<!-- Content is outside the core-loading to let the script calculate drag items position -->
|
||||
<core-loading [hideUntil]="ddQuestion.loaded"></core-loading>
|
||||
<core-loading [hideUntil]="question.loaded"></core-loading>
|
||||
|
||||
<ion-item class="ion-text-wrap" [hidden]="!ddQuestion.loaded">
|
||||
<ion-item class="ion-text-wrap" [hidden]="!question.loaded">
|
||||
<ion-label>
|
||||
<ion-card *ngIf="!ddQuestion.readOnly" class="core-info-card">
|
||||
<ion-card *ngIf="!question.readOnly" class="core-info-card">
|
||||
<ion-item>
|
||||
<ion-icon name="fas-info-circle" slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-label>{{ 'core.question.howtodraganddrop' | translate }}</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="ddQuestion.text" #questiontext
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.text" #questiontext
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" (afterRender)="textRendered()">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<div class="fake-ion-item ion-text-wrap" [hidden]="!ddQuestion.loaded">
|
||||
<core-format-text *ngIf="ddQuestion.ddArea" [adaptImg]="false" [component]="component" [componentId]="componentId"
|
||||
[text]="ddQuestion.ddArea" [filter]="false" (afterRender)="ddAreaRendered()">
|
||||
<div class="fake-ion-item ion-text-wrap" [hidden]="!question.loaded">
|
||||
<core-format-text *ngIf="question.ddArea" [adaptImg]="false" [component]="component" [componentId]="componentId"
|
||||
[text]="question.ddArea" [filter]="false" (afterRender)="ddAreaRendered()">
|
||||
</core-format-text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Component, OnDestroy, ElementRef, ViewChild } from '@angular/core';
|
||||
|
||||
import { AddonModQuizQuestionBasicData, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
import { CoreQuestionHelper } from '@features/question/services/question-helper';
|
||||
|
@ -29,12 +29,12 @@ import { AddonQtypeDdMarkerQuestion } from '../classes/ddmarker';
|
|||
templateUrl: 'addon-qtype-ddmarker.html',
|
||||
styleUrls: ['ddmarker.scss'],
|
||||
})
|
||||
export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent implements OnInit, OnDestroy {
|
||||
export class AddonQtypeDdMarkerComponent
|
||||
extends CoreQuestionBaseComponent<AddonQtypeDdMarkerQuestionData>
|
||||
implements OnDestroy {
|
||||
|
||||
@ViewChild('questiontext') questionTextEl?: ElementRef;
|
||||
|
||||
ddQuestion?: AddonQtypeDdMarkerQuestionData;
|
||||
|
||||
protected questionInstance?: AddonQtypeDdMarkerQuestion;
|
||||
protected dropZones: unknown[] = []; // The drop zones received in the init object of the question.
|
||||
protected imgSrc?: string; // Background image URL.
|
||||
|
@ -49,65 +49,64 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
if (!this.question) {
|
||||
this.logger.warn('Aborting because of no question received.');
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
return;
|
||||
}
|
||||
|
||||
this.ddQuestion = this.question;
|
||||
const element = CoreDomUtils.convertToElement(this.question.html);
|
||||
const questionElement = this.initComponent();
|
||||
if (!questionElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get D&D area, form and question text.
|
||||
const ddArea = element.querySelector('.ddarea');
|
||||
const ddForm = element.querySelector('.ddform');
|
||||
const ddArea = questionElement.querySelector('.ddarea');
|
||||
const ddForm = questionElement.querySelector('.ddform');
|
||||
|
||||
this.ddQuestion.text = CoreDomUtils.getContentsOfElement(element, '.qtext');
|
||||
if (!ddArea || !ddForm || this.ddQuestion.text === undefined) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.ddQuestion.slot);
|
||||
if (!ddArea || !ddForm) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.question.slot);
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
|
||||
// Build the D&D area HTML.
|
||||
this.ddQuestion.ddArea = ddArea.outerHTML;
|
||||
this.question.ddArea = ddArea.outerHTML;
|
||||
|
||||
const wrongParts = element.querySelector('.wrongparts');
|
||||
const wrongParts = questionElement.querySelector('.wrongparts');
|
||||
if (wrongParts) {
|
||||
this.ddQuestion.ddArea += wrongParts.outerHTML;
|
||||
this.question.ddArea += wrongParts.outerHTML;
|
||||
}
|
||||
this.ddQuestion.ddArea += ddForm.outerHTML;
|
||||
this.ddQuestion.readOnly = false;
|
||||
this.question.ddArea += ddForm.outerHTML;
|
||||
this.question.readOnly = false;
|
||||
|
||||
if (this.ddQuestion.initObjects) {
|
||||
if (this.question.initObjects) {
|
||||
// Moodle version = 3.5.
|
||||
if (this.ddQuestion.initObjects.dropzones !== undefined) {
|
||||
this.dropZones = <unknown[]> this.ddQuestion.initObjects.dropzones;
|
||||
if (this.question.initObjects.dropzones !== undefined) {
|
||||
this.dropZones = <unknown[]> this.question.initObjects.dropzones;
|
||||
}
|
||||
if (this.ddQuestion.initObjects.readonly !== undefined) {
|
||||
this.ddQuestion.readOnly = !!this.ddQuestion.initObjects.readonly;
|
||||
if (this.question.initObjects.readonly !== undefined) {
|
||||
this.question.readOnly = !!this.question.initObjects.readonly;
|
||||
}
|
||||
} else if (this.ddQuestion.amdArgs) {
|
||||
} else if (this.question.amdArgs) {
|
||||
// Moodle version >= 3.6.
|
||||
let nextIndex = 1;
|
||||
// Moodle version >= 3.9, imgSrc is not specified, do not advance index.
|
||||
if (this.ddQuestion.amdArgs[nextIndex] !== undefined && typeof this.ddQuestion.amdArgs[nextIndex] != 'boolean') {
|
||||
this.imgSrc = <string> this.ddQuestion.amdArgs[nextIndex];
|
||||
if (this.question.amdArgs[nextIndex] !== undefined && typeof this.question.amdArgs[nextIndex] !== 'boolean') {
|
||||
this.imgSrc = <string> this.question.amdArgs[nextIndex];
|
||||
nextIndex++;
|
||||
}
|
||||
|
||||
if (this.ddQuestion.amdArgs[nextIndex] !== undefined) {
|
||||
this.ddQuestion.readOnly = !!this.ddQuestion.amdArgs[nextIndex];
|
||||
if (this.question.amdArgs[nextIndex] !== undefined) {
|
||||
this.question.readOnly = !!this.question.amdArgs[nextIndex];
|
||||
}
|
||||
nextIndex++;
|
||||
|
||||
if (this.ddQuestion.amdArgs[nextIndex] !== undefined) {
|
||||
this.dropZones = <unknown[]> this.ddQuestion.amdArgs[nextIndex];
|
||||
if (this.question.amdArgs[nextIndex] !== undefined) {
|
||||
this.dropZones = <unknown[]> this.question.amdArgs[nextIndex];
|
||||
}
|
||||
}
|
||||
|
||||
this.ddQuestion.loaded = false;
|
||||
this.question.loaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,9 +133,10 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple
|
|||
* The question has been rendered.
|
||||
*/
|
||||
protected async questionRendered(): Promise<void> {
|
||||
if (this.destroyed) {
|
||||
if (this.destroyed || !this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Download background image (3.6+ sites).
|
||||
let imgSrc = this.imgSrc;
|
||||
const site = CoreSites.getCurrentSite();
|
||||
|
@ -160,8 +160,8 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple
|
|||
// Create the instance.
|
||||
this.questionInstance = new AddonQtypeDdMarkerQuestion(
|
||||
this.hostElement,
|
||||
this.ddQuestion!,
|
||||
!!this.ddQuestion!.readOnly,
|
||||
this.question,
|
||||
!!this.question.readOnly,
|
||||
this.dropZones,
|
||||
imgSrc,
|
||||
);
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
<div *ngIf="ddQuestion && (ddQuestion.text || ddQuestion.text === '')">
|
||||
<div *ngIf="question && (question.text || question.text === '')">
|
||||
<!-- Content is outside the core-loading to let the script calculate drag items position -->
|
||||
<core-loading [hideUntil]="ddQuestion.loaded"></core-loading>
|
||||
<core-loading [hideUntil]="question.loaded"></core-loading>
|
||||
|
||||
<div class="fake-ion-item ion-text-wrap" [hidden]="!ddQuestion.loaded">
|
||||
<ion-card *ngIf="!ddQuestion.readOnly" class="core-info-card">
|
||||
<div class="fake-ion-item ion-text-wrap" [hidden]="!question.loaded">
|
||||
<ion-card *ngIf="!question.readOnly" class="core-info-card">
|
||||
<ion-item>
|
||||
<ion-icon name="fas-info-circle" slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-label>{{ 'core.question.howtodraganddrop' | translate }}</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
<div class="addon-qtype-ddwtos-container">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="ddQuestion.text" [contextLevel]="contextLevel"
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId" #questiontext (afterRender)="textRendered()">
|
||||
</core-format-text>
|
||||
|
||||
<core-format-text *ngIf="ddQuestion.answers" [component]="component" [componentId]="componentId" [text]="ddQuestion.answers"
|
||||
<core-format-text *ngIf="question.answers" [component]="component" [componentId]="componentId" [text]="question.answers"
|
||||
[filter]="false" (afterRender)="answersRendered()">
|
||||
</core-format-text>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Component, OnDestroy, ElementRef, ViewChild } from '@angular/core';
|
||||
|
||||
import { AddonModQuizQuestionBasicData, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
import { CoreQuestionHelper } from '@features/question/services/question-helper';
|
||||
|
@ -27,12 +27,10 @@ import { AddonQtypeDdwtosQuestion } from '../classes/ddwtos';
|
|||
templateUrl: 'addon-qtype-ddwtos.html',
|
||||
styleUrls: ['ddwtos.scss'],
|
||||
})
|
||||
export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent implements OnInit, OnDestroy {
|
||||
export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent<AddonModQuizDdwtosQuestionData> implements OnDestroy {
|
||||
|
||||
@ViewChild('questiontext') questionTextEl?: ElementRef;
|
||||
|
||||
ddQuestion?: AddonModQuizDdwtosQuestionData;
|
||||
|
||||
protected questionInstance?: AddonQtypeDdwtosQuestion;
|
||||
protected inputIds: string[] = []; // Ids of the inputs of the question (where the answers will be stored).
|
||||
protected destroyed = false;
|
||||
|
@ -46,52 +44,50 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
if (!this.question) {
|
||||
this.logger.warn('Aborting because of no question received.');
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
return;
|
||||
}
|
||||
|
||||
this.ddQuestion = this.question;
|
||||
const element = CoreDomUtils.convertToElement(this.ddQuestion.html);
|
||||
const questionElement = this.initComponent();
|
||||
if (!questionElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace Moodle's correct/incorrect and feedback classes with our own.
|
||||
CoreQuestionHelper.replaceCorrectnessClasses(element);
|
||||
CoreQuestionHelper.replaceFeedbackClasses(element);
|
||||
CoreQuestionHelper.replaceCorrectnessClasses(questionElement);
|
||||
CoreQuestionHelper.replaceFeedbackClasses(questionElement);
|
||||
|
||||
// Treat the correct/incorrect icons.
|
||||
CoreQuestionHelper.treatCorrectnessIcons(element);
|
||||
CoreQuestionHelper.treatCorrectnessIcons(questionElement);
|
||||
|
||||
const answerContainer = element.querySelector('.answercontainer');
|
||||
const answerContainer = questionElement.querySelector('.answercontainer');
|
||||
if (!answerContainer) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.ddQuestion.slot);
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.question.slot);
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
|
||||
this.ddQuestion.readOnly = answerContainer.classList.contains('readonly');
|
||||
this.ddQuestion.answers = answerContainer.outerHTML;
|
||||
|
||||
this.ddQuestion.text = CoreDomUtils.getContentsOfElement(element, '.qtext');
|
||||
if (this.ddQuestion.text === undefined) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.ddQuestion.slot);
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
this.question.readOnly = answerContainer.classList.contains('readonly');
|
||||
this.question.answers = answerContainer.outerHTML;
|
||||
|
||||
// Get the inputs where the answers will be stored and add them to the question text.
|
||||
const inputEls = <HTMLElement[]> Array.from(element.querySelectorAll('input[type="hidden"]:not([name*=sequencecheck])'));
|
||||
const inputEls = Array.from(
|
||||
questionElement.querySelectorAll<HTMLInputElement>('input[type="hidden"]:not([name*=sequencecheck])'),
|
||||
);
|
||||
|
||||
let questionText = this.question.text;
|
||||
inputEls.forEach((inputEl) => {
|
||||
this.ddQuestion!.text += inputEl.outerHTML;
|
||||
questionText += inputEl.outerHTML;
|
||||
const id = inputEl.getAttribute('id');
|
||||
if (id) {
|
||||
this.inputIds.push(id);
|
||||
}
|
||||
});
|
||||
|
||||
this.ddQuestion.loaded = false;
|
||||
this.question.text = questionText;
|
||||
|
||||
this.question.loaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,7 +114,7 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme
|
|||
* The question has been rendered.
|
||||
*/
|
||||
protected async questionRendered(): Promise<void> {
|
||||
if (this.destroyed) {
|
||||
if (this.destroyed || !this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -129,8 +125,8 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme
|
|||
// Create the instance.
|
||||
this.questionInstance = new AddonQtypeDdwtosQuestion(
|
||||
this.hostElement,
|
||||
this.ddQuestion!,
|
||||
!!this.ddQuestion!.readOnly,
|
||||
this.question,
|
||||
!!this.question.readOnly,
|
||||
this.inputIds,
|
||||
);
|
||||
|
||||
|
@ -143,7 +139,7 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme
|
|||
this.courseId,
|
||||
);
|
||||
|
||||
this.ddQuestion!.loaded = true;
|
||||
this.question.loaded = true;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
|
||||
import { Component, ElementRef } from '@angular/core';
|
||||
import { CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
|
@ -23,7 +22,7 @@ import { CoreQuestionBaseComponent } from '@features/question/classes/base-quest
|
|||
selector: 'addon-qtype-description',
|
||||
templateUrl: 'addon-qtype-description.html',
|
||||
})
|
||||
export class AddonQtypeDescriptionComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
export class AddonQtypeDescriptionComponent extends CoreQuestionBaseComponent {
|
||||
|
||||
seenInput?: { name: string; value: string };
|
||||
|
||||
|
@ -34,20 +33,22 @@ export class AddonQtypeDescriptionComponent extends CoreQuestionBaseComponent im
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
const questionEl = this.initComponent();
|
||||
if (!questionEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the "seen" hidden input.
|
||||
const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=seen]');
|
||||
if (input) {
|
||||
this.seenInput = {
|
||||
name: input.name,
|
||||
value: input.value,
|
||||
};
|
||||
const input = questionEl.querySelector<HTMLInputElement>('input[type="hidden"][name*=seen]');
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.seenInput = {
|
||||
name: input.name,
|
||||
value: input.value,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<ion-list *ngIf="essayQuestion && (essayQuestion.text || essayQuestion.text === '')">
|
||||
<ion-list *ngIf="question && (question.text || question.text === '')">
|
||||
<!-- Question text. -->
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="essayQuestion.text" [contextLevel]="contextLevel"
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
|
@ -11,27 +11,26 @@
|
|||
<!-- Editing the question. -->
|
||||
<ng-container *ngIf="!review">
|
||||
<!-- Textarea. -->
|
||||
<ion-item *ngIf="essayQuestion.textarea && (!essayQuestion.hasDraftFiles || uploadFilesSupported)">
|
||||
<ion-item *ngIf="question.textarea && (!question.hasDraftFiles || uploadFilesSupported)">
|
||||
<ion-label class="sr-only">{{ 'core.question.answer' | translate }}</ion-label>
|
||||
<!-- "Format" and draftid hidden inputs -->
|
||||
<input *ngIf="essayQuestion.formatInput" type="hidden" [name]="essayQuestion.formatInput.name"
|
||||
[value]="essayQuestion.formatInput.value">
|
||||
<input *ngIf="essayQuestion.answerDraftIdInput" type="hidden" [name]="essayQuestion.answerDraftIdInput.name"
|
||||
[value]="essayQuestion.answerDraftIdInput.value">
|
||||
<input *ngIf="question.formatInput" type="hidden" [name]="question.formatInput.name" [value]="question.formatInput.value">
|
||||
<input *ngIf="question.answerDraftIdInput" type="hidden" [name]="question.answerDraftIdInput.name"
|
||||
[value]="question.answerDraftIdInput.value">
|
||||
<!-- Plain text textarea. -->
|
||||
<ion-textarea *ngIf="essayQuestion.isPlainText" class="core-question-textarea"
|
||||
[ngClass]='{"core-monospaced": essayQuestion.isMonospaced}' placeholder="{{ 'core.question.answer' | translate }}"
|
||||
[attr.name]="essayQuestion.textarea.name" [ngModel]="essayQuestion.textarea.text">
|
||||
<ion-textarea *ngIf="question.isPlainText" class="core-question-textarea" [ngClass]='{"core-monospaced": question.isMonospaced}'
|
||||
placeholder="{{ 'core.question.answer' | translate }}" [attr.name]="question.textarea.name"
|
||||
[ngModel]="question.textarea.text">
|
||||
</ion-textarea>
|
||||
<!-- Rich text editor. -->
|
||||
<core-rich-text-editor *ngIf="!essayQuestion.isPlainText" placeholder="{{ 'core.question.answer' | translate }}"
|
||||
[control]="formControl" [name]="essayQuestion.textarea.name" [component]="component" [componentId]="componentId"
|
||||
<core-rich-text-editor *ngIf="!question.isPlainText" placeholder="{{ 'core.question.answer' | translate }}"
|
||||
[control]="formControl" [name]="question.textarea.name" [component]="component" [componentId]="componentId"
|
||||
[autoSave]="false">
|
||||
</core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
||||
<!-- Draft files not supported. -->
|
||||
<ng-container *ngIf="essayQuestion.textarea && essayQuestion.hasDraftFiles && !uploadFilesSupported">
|
||||
<ng-container *ngIf="question.textarea && question.hasDraftFiles && !uploadFilesSupported">
|
||||
<ion-item class="ion-text-wrap core-danger-item">
|
||||
<ion-label class="core-question-warning">
|
||||
{{ 'core.question.errorembeddedfilesnotsupportedinsite' | translate }}
|
||||
|
@ -39,7 +38,7 @@
|
|||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="essayQuestion.textarea.text"
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.textarea.text"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
|
@ -47,15 +46,14 @@
|
|||
</ng-container>
|
||||
|
||||
<!-- Attachments. -->
|
||||
<ng-container *ngIf="essayQuestion.allowsAttachments">
|
||||
<core-attachments *ngIf="uploadFilesSupported && essayQuestion.attachmentsDraftIdInput" [files]="attachments"
|
||||
[component]="component" [componentId]="componentId" [maxSize]="essayQuestion.attachmentsMaxBytes"
|
||||
[maxSubmissions]="essayQuestion.attachmentsMaxFiles" [allowOffline]="offlineEnabled"
|
||||
[acceptedTypes]="essayQuestion.attachmentsAcceptedTypes" [courseId]="courseId">
|
||||
<ng-container *ngIf="question.allowsAttachments">
|
||||
<core-attachments *ngIf="uploadFilesSupported && question.attachmentsDraftIdInput" [files]="attachments" [component]="component"
|
||||
[componentId]="componentId" [maxSize]="question.attachmentsMaxBytes" [maxSubmissions]="question.attachmentsMaxFiles"
|
||||
[allowOffline]="offlineEnabled" [acceptedTypes]="question.attachmentsAcceptedTypes" [courseId]="courseId">
|
||||
</core-attachments>
|
||||
|
||||
<input *ngIf="essayQuestion.attachmentsDraftIdInput" type="hidden" [name]="essayQuestion.attachmentsDraftIdInput.name"
|
||||
[value]="essayQuestion.attachmentsDraftIdInput.value">
|
||||
<input *ngIf="question.attachmentsDraftIdInput" type="hidden" [name]="question.attachmentsDraftIdInput.name"
|
||||
[value]="question.attachmentsDraftIdInput.value">
|
||||
|
||||
<!-- Attachments not supported in this site. -->
|
||||
<ion-item *ngIf="!uploadFilesSupported" class="ion-text-wrap core-danger-item">
|
||||
|
@ -69,36 +67,35 @@
|
|||
<!-- Reviewing the question. -->
|
||||
<ng-container *ngIf="review">
|
||||
<!-- Answer to the question and attachments (reviewing). -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="essayQuestion.answer || essayQuestion.answer == ''">
|
||||
<ion-item class="ion-text-wrap" *ngIf="question.answer || question.answer == ''">
|
||||
<ion-label>
|
||||
<core-format-text [ngClass]='{"core-monospaced": essayQuestion.isMonospaced}' [component]="component"
|
||||
[componentId]="componentId" [text]="essayQuestion.answer" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
<core-format-text [ngClass]='{"core-monospaced": question.isMonospaced}' [component]="component" [componentId]="componentId"
|
||||
[text]="question.answer" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- Word count info. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="essayQuestion.wordCountInfo">
|
||||
<ion-item class="ion-text-wrap" *ngIf="question.wordCountInfo">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="essayQuestion.wordCountInfo"
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.wordCountInfo"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- Answer plagiarism. -->
|
||||
<ion-item class="ion-text-wrap" *ngIf="essayQuestion.answerPlagiarism">
|
||||
<ion-item class="ion-text-wrap" *ngIf="question.answerPlagiarism">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="essayQuestion.answerPlagiarism"
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.answerPlagiarism"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- List of attachments when reviewing. -->
|
||||
<core-files *ngIf="essayQuestion.attachments" [files]="essayQuestion.attachments" [component]="component"
|
||||
[componentId]="componentId" [extraHtml]="essayQuestion.attachmentsPlagiarisms">
|
||||
<core-files *ngIf="question.attachments" [files]="question.attachments" [component]="component" [componentId]="componentId"
|
||||
[extraHtml]="question.attachmentsPlagiarisms">
|
||||
</core-files>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import { Component, ElementRef } from '@angular/core';
|
||||
import { FormBuilder, FormControl } from '@angular/forms';
|
||||
import { FileEntry } from '@ionic-native/file/ngx';
|
||||
|
||||
|
@ -30,12 +30,11 @@ import { CoreFileEntry } from '@services/file-helper';
|
|||
selector: 'addon-qtype-essay',
|
||||
templateUrl: 'addon-qtype-essay.html',
|
||||
})
|
||||
export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent<AddonModQuizEssayQuestion> {
|
||||
|
||||
formControl?: FormControl;
|
||||
attachments?: CoreFileEntry[];
|
||||
uploadFilesSupported = false;
|
||||
essayQuestion?: AddonModQuizEssayQuestion;
|
||||
|
||||
constructor(elementRef: ElementRef, protected fb: FormBuilder) {
|
||||
super('AddonQtypeEssayComponent', elementRef);
|
||||
|
@ -44,14 +43,18 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implemen
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.uploadFilesSupported = this.question?.responsefileareas !== undefined;
|
||||
init(): void {
|
||||
if (!this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFilesSupported = this.question.responsefileareas !== undefined;
|
||||
|
||||
this.initEssayComponent(this.review);
|
||||
this.essayQuestion = this.question;
|
||||
|
||||
this.formControl = this.fb.control(this.essayQuestion?.textarea?.text);
|
||||
this.formControl = this.fb.control(this.question?.textarea?.text);
|
||||
|
||||
if (this.essayQuestion?.allowsAttachments && this.uploadFilesSupported && !this.review) {
|
||||
if (this.question?.allowsAttachments && this.uploadFilesSupported && !this.review) {
|
||||
this.loadAttachments();
|
||||
}
|
||||
}
|
||||
|
@ -62,10 +65,14 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implemen
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async loadAttachments(): Promise<void> {
|
||||
if (this.offlineEnabled && this.essayQuestion?.localAnswers?.attachments_offline) {
|
||||
if (!this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.offlineEnabled && this.question.localAnswers?.attachments_offline) {
|
||||
|
||||
const attachmentsData: CoreFileUploaderStoreFilesResult = CoreTextUtils.parseJSON(
|
||||
this.essayQuestion.localAnswers.attachments_offline,
|
||||
this.question.localAnswers.attachments_offline,
|
||||
{
|
||||
online: [],
|
||||
offline: 0,
|
||||
|
@ -75,7 +82,7 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implemen
|
|||
|
||||
if (attachmentsData.offline) {
|
||||
offlineFiles = <FileEntry[]> await CoreQuestionHelper.getStoredQuestionFiles(
|
||||
this.essayQuestion,
|
||||
this.question,
|
||||
this.component || '',
|
||||
this.componentId || -1,
|
||||
);
|
||||
|
@ -83,12 +90,12 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implemen
|
|||
|
||||
this.attachments = [...attachmentsData.online, ...offlineFiles];
|
||||
} else {
|
||||
this.attachments = Array.from(CoreQuestionHelper.getResponseFileAreaFiles(this.question!, 'attachments'));
|
||||
this.attachments = Array.from(CoreQuestionHelper.getResponseFileAreaFiles(this.question, 'attachments'));
|
||||
}
|
||||
|
||||
CoreFileSession.setFiles(
|
||||
this.component || '',
|
||||
CoreQuestion.getQuestionComponentId(this.question!, this.componentId || -1),
|
||||
CoreQuestion.getQuestionComponentId(this.question, this.componentId || -1),
|
||||
this.attachments,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import { Component, ElementRef } from '@angular/core';
|
||||
|
||||
import { CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
import { CoreQuestionHelper } from '@features/question/services/question-helper';
|
||||
|
@ -25,7 +25,7 @@ import { CoreQuestionHelper } from '@features/question/services/question-helper'
|
|||
templateUrl: 'addon-qtype-gapselect.html',
|
||||
styleUrls: ['gapselect.scss'],
|
||||
})
|
||||
export class AddonQtypeGapSelectComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
export class AddonQtypeGapSelectComponent extends CoreQuestionBaseComponent {
|
||||
|
||||
constructor(elementRef: ElementRef) {
|
||||
super('AddonQtypeGapSelectComponent', elementRef);
|
||||
|
@ -34,7 +34,7 @@ export class AddonQtypeGapSelectComponent extends CoreQuestionBaseComponent impl
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
this.initOriginalTextComponent('.qtext');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<section class="addon-qtype-match-container" *ngIf="matchQuestion && matchQuestion.loaded">
|
||||
<section class="addon-qtype-match-container" *ngIf="question && question.loaded">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="matchQuestion.text" [contextLevel]="contextLevel"
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let row of matchQuestion.rows">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let row of question.rows">
|
||||
<ion-label>
|
||||
<core-format-text id="addon-qtype-match-question-{{row.id}}" [component]="component" [componentId]="componentId"
|
||||
[text]="row.text" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import { Component, ElementRef } from '@angular/core';
|
||||
|
||||
import { AddonModQuizMatchQuestion, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
|
||||
|
@ -24,9 +24,7 @@ import { AddonModQuizMatchQuestion, CoreQuestionBaseComponent } from '@features/
|
|||
templateUrl: 'addon-qtype-match.html',
|
||||
styleUrls: ['match.scss'],
|
||||
})
|
||||
export class AddonQtypeMatchComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
matchQuestion?: AddonModQuizMatchQuestion;
|
||||
export class AddonQtypeMatchComponent extends CoreQuestionBaseComponent<AddonModQuizMatchQuestion> {
|
||||
|
||||
constructor(elementRef: ElementRef) {
|
||||
super('AddonQtypeMatchComponent', elementRef);
|
||||
|
@ -35,9 +33,8 @@ export class AddonQtypeMatchComponent extends CoreQuestionBaseComponent implemen
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
this.initMatchComponent();
|
||||
this.matchQuestion = this.question;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import { Component, ElementRef } from '@angular/core';
|
||||
import { CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
import { CoreQuestionHelper } from '@features/question/services/question-helper';
|
||||
|
||||
|
@ -24,7 +24,7 @@ import { CoreQuestionHelper } from '@features/question/services/question-helper'
|
|||
templateUrl: 'addon-qtype-multianswer.html',
|
||||
styleUrls: ['multianswer.scss'],
|
||||
})
|
||||
export class AddonQtypeMultiAnswerComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
export class AddonQtypeMultiAnswerComponent extends CoreQuestionBaseComponent {
|
||||
|
||||
constructor(elementRef: ElementRef) {
|
||||
super('AddonQtypeMultiAnswerComponent', elementRef);
|
||||
|
@ -33,7 +33,7 @@ export class AddonQtypeMultiAnswerComponent extends CoreQuestionBaseComponent im
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
this.initOriginalTextComponent('.formulation');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
<ion-list *ngIf="multiQuestion && (multiQuestion.text || multiQuestion.text === '')">
|
||||
<ion-list *ngIf="question && (question.text || question.text === '')">
|
||||
<!-- Question text first. -->
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="multiQuestion.text"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
<p *ngIf="multiQuestion.prompt">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="multiQuestion.prompt"
|
||||
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
<p *ngIf="question.prompt">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.prompt" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- Checkbox for multiple choice. -->
|
||||
<ng-container *ngIf="multiQuestion.multi">
|
||||
<ion-item class="ion-text-wrap answer" *ngFor="let option of multiQuestion.options">
|
||||
<ng-container *ngIf="question.multi">
|
||||
<ion-item class="ion-text-wrap answer" *ngFor="let option of question.options">
|
||||
<ion-label [color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")' [class]="option.class">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
|
@ -44,8 +44,8 @@
|
|||
</ng-container>
|
||||
|
||||
<!-- Radio buttons for single choice. -->
|
||||
<ion-radio-group *ngIf="!multiQuestion.multi" [(ngModel)]="multiQuestion.singleChoiceModel" [name]="multiQuestion.optionsName">
|
||||
<ion-item class="ion-text-wrap answer" *ngFor="let option of multiQuestion.options">
|
||||
<ion-radio-group *ngIf="!question.multi" [(ngModel)]="question.singleChoiceModel" [name]="question.optionsName">
|
||||
<ion-item class="ion-text-wrap answer" *ngFor="let option of question.options">
|
||||
<ion-label [class]="option.class">
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="option.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
|
@ -66,12 +66,12 @@
|
|||
<ion-icon *ngIf="option.isCorrect === 0" class="core-correct-icon" name="fas-times" color="danger"
|
||||
[attr.aria-label]="'core.question.incorrect' | translate"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-button *ngIf="!multiQuestion.disabled" class="ion-text-wrap ion-margin-top" expand="block" fill="outline"
|
||||
[disabled]="!multiQuestion.singleChoiceModel" (click)="clear()" type="button">
|
||||
<ion-button *ngIf="!question.disabled" class="ion-text-wrap ion-margin-top" expand="block" fill="outline"
|
||||
[disabled]="!question.singleChoiceModel" (click)="clear()" type="button">
|
||||
{{ 'addon.mod_quiz.clearchoice' | translate }}
|
||||
</ion-button>
|
||||
|
||||
<!-- ion-radio doesn't use an input. Create a hidden input to hold the selected value. -->
|
||||
<input type="hidden" [ngModel]="multiQuestion.singleChoiceModel" [attr.name]="multiQuestion.optionsName">
|
||||
<input type="hidden" [ngModel]="question.singleChoiceModel" [attr.name]="question.optionsName">
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import { Component, ElementRef } from '@angular/core';
|
||||
|
||||
import { AddonModQuizMultichoiceQuestion, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
|
||||
|
@ -24,9 +24,7 @@ import { AddonModQuizMultichoiceQuestion, CoreQuestionBaseComponent } from '@fea
|
|||
templateUrl: 'addon-qtype-multichoice.html',
|
||||
styleUrls: ['multichoice.scss'],
|
||||
})
|
||||
export class AddonQtypeMultichoiceComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
multiQuestion?: AddonModQuizMultichoiceQuestion;
|
||||
export class AddonQtypeMultichoiceComponent extends CoreQuestionBaseComponent<AddonModQuizMultichoiceQuestion> {
|
||||
|
||||
constructor(elementRef: ElementRef) {
|
||||
super('AddonQtypeMultichoiceComponent', elementRef);
|
||||
|
@ -35,16 +33,19 @@ export class AddonQtypeMultichoiceComponent extends CoreQuestionBaseComponent im
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
this.initMultichoiceComponent();
|
||||
this.multiQuestion = this.question;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear selected choices.
|
||||
*/
|
||||
clear(): void {
|
||||
this.multiQuestion!.singleChoiceModel = undefined;
|
||||
if (!this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.question.singleChoiceModel = undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ export class AddonQtypeMultichoiceHandlerService implements CoreQuestionHandler
|
|||
|
||||
// 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) {
|
||||
if (name.indexOf('choice') !== -1) {
|
||||
isSingle = false;
|
||||
if (!CoreUtils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, name)) {
|
||||
isMultiSame = false;
|
||||
|
@ -128,9 +128,9 @@ export class AddonQtypeMultichoiceHandlerService implements CoreQuestionHandler
|
|||
|
||||
if (isSingle) {
|
||||
return this.isSameResponseSingle(prevAnswers, newAnswers);
|
||||
} else {
|
||||
return isMultiSame;
|
||||
}
|
||||
|
||||
return isMultiSame;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,10 +151,11 @@ export class AddonQtypeMultichoiceHandlerService implements CoreQuestionHandler
|
|||
question: AddonModQuizMultichoiceQuestion,
|
||||
answers: CoreQuestionsAnswers,
|
||||
): void {
|
||||
if (question && !question.multi && answers[question.optionsName!] !== undefined && !answers[question.optionsName!]) {
|
||||
if (question && !question.multi &&
|
||||
question.optionsName && answers[question.optionsName] !== undefined && !answers[question.optionsName]) {
|
||||
/* It's a single choice and the user hasn't answered. Delete the answer because
|
||||
sending an empty string (default value) will mark the first option as selected. */
|
||||
delete answers[question.optionsName!];
|
||||
delete answers[question.optionsName];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
<ion-list *ngIf="textQuestion && (textQuestion.text || textQuestion.text === '')">
|
||||
<ion-list *ngIf="question && (question.text || question.text === '')">
|
||||
<ion-item class="ion-text-wrap addon-qtype-shortanswer-text">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="textQuestion.text" [contextLevel]="contextLevel"
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="question.text" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="textQuestion.input && !textQuestion.input.isInline"
|
||||
class="ion-text-wrap addon-qtype-shortanswer-input core-{{textQuestion.input.correctIconColor}}-item">
|
||||
<ion-item *ngIf="question.input && !question.input.isInline"
|
||||
class="ion-text-wrap addon-qtype-shortanswer-input core-{{question.input.correctIconColor}}-item">
|
||||
<ion-label position="stacked">{{ 'addon.mod_quiz.answercolon' | translate }}</ion-label>
|
||||
<ion-input type="text" [placeholder]="textQuestion.input.readOnly ? '' : 'core.question.answer' | translate"
|
||||
[attr.name]="textQuestion.input.name" [value]="textQuestion.input.value" autocorrect="off"
|
||||
[disabled]="textQuestion.input.readOnly">
|
||||
<ion-input type="text" [placeholder]="question.input.readOnly ? '' : 'core.question.answer' | translate"
|
||||
[attr.name]="question.input.name" [value]="question.input.value" autocorrect="off" [disabled]="question.input.readOnly">
|
||||
</ion-input>
|
||||
<ion-icon *ngIf="textQuestion.input.correctIcon" class="core-correct-icon" slot="end" [name]="textQuestion.input.correctIcon"
|
||||
[color]="[textQuestion.input.correctIconColor]">
|
||||
<ion-icon *ngIf="question.input.correctIcon" class="core-correct-icon" slot="end" [name]="question.input.correctIcon"
|
||||
[color]="[question.input.correctIconColor]">
|
||||
</ion-icon>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import { Component, ElementRef } from '@angular/core';
|
||||
|
||||
import { AddonModQuizTextQuestion, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
|
||||
|
||||
|
@ -24,9 +24,7 @@ import { AddonModQuizTextQuestion, CoreQuestionBaseComponent } from '@features/q
|
|||
templateUrl: 'addon-qtype-shortanswer.html',
|
||||
styleUrls: ['shortanswer.scss'],
|
||||
})
|
||||
export class AddonQtypeShortAnswerComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
textQuestion?: AddonModQuizTextQuestion;
|
||||
export class AddonQtypeShortAnswerComponent extends CoreQuestionBaseComponent<AddonModQuizTextQuestion> {
|
||||
|
||||
constructor(elementRef: ElementRef) {
|
||||
super('AddonQtypeShortAnswerComponent', elementRef);
|
||||
|
@ -35,9 +33,8 @@ export class AddonQtypeShortAnswerComponent extends CoreQuestionBaseComponent im
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
init(): void {
|
||||
this.initInputTextComponent();
|
||||
this.textQuestion = this.question;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -83,9 +83,9 @@ export class AddonQtypeTrueFalseHandlerService implements CoreQuestionHandler {
|
|||
question: AddonModQuizMultichoiceQuestion,
|
||||
answers: CoreQuestionsAnswers,
|
||||
): void | Promise<void> {
|
||||
if (question && answers[question.optionsName!] !== undefined && !answers[question.optionsName!]) {
|
||||
if (question && question.optionsName && answers[question.optionsName] !== undefined && !answers[question.optionsName]) {
|
||||
// The user hasn't answered. Delete the answer to prevent marking one of the answers automatically.
|
||||
delete answers[question.optionsName!];
|
||||
delete answers[question.optionsName];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Input, Output, EventEmitter, Component, Optional, Inject, ElementRef } from '@angular/core';
|
||||
import { Input, Output, EventEmitter, Component, Optional, Inject, ElementRef, OnInit } from '@angular/core';
|
||||
import { CoreFileHelper } from '@services/file-helper';
|
||||
|
||||
import { CoreSites } from '@services/sites';
|
||||
|
@ -20,6 +20,7 @@ import { CoreDomUtils } from '@services/utils/dom';
|
|||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { CoreWSFile } from '@services/ws';
|
||||
import { CoreIonicColorNames } from '@singletons/colors';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreQuestionBehaviourButton, CoreQuestionHelper, CoreQuestionQuestion } from '../services/question-helper';
|
||||
|
||||
|
@ -29,9 +30,9 @@ import { CoreQuestionBehaviourButton, CoreQuestionHelper, CoreQuestionQuestion }
|
|||
@Component({
|
||||
template: '',
|
||||
})
|
||||
export class CoreQuestionBaseComponent {
|
||||
export class CoreQuestionBaseComponent<T extends AddonModQuizQuestion = AddonModQuizQuestion> implements OnInit {
|
||||
|
||||
@Input() question?: AddonModQuizQuestion; // The question to render.
|
||||
@Input() question?: T; // The question to render.
|
||||
@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.
|
||||
|
@ -52,6 +53,51 @@ export class CoreQuestionBaseComponent {
|
|||
this.hostElement = elementRef.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (!this.question) {
|
||||
this.logger.warn('Aborting because of no question received.');
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the question component, override it if needed.
|
||||
*/
|
||||
init(): void {
|
||||
this.initComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the component and the question text.
|
||||
*
|
||||
* @returns Element containing the question HTML, void if the data is not valid.
|
||||
*/
|
||||
initComponent(): void | HTMLElement {
|
||||
if (!this.question) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hostElement.classList.add('core-question-container');
|
||||
|
||||
const questionElement = CoreDomUtils.convertToElement(this.question.html);
|
||||
|
||||
// Extract question text.
|
||||
this.question.text = CoreDomUtils.getContentsOfElement(questionElement, '.qtext');
|
||||
if (this.question.text === undefined) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.question.slot);
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
|
||||
return questionElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a question component of type calculated or calculated simple.
|
||||
*
|
||||
|
@ -207,33 +253,6 @@ export class CoreQuestionBaseComponent {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the component and the question text.
|
||||
*
|
||||
* @returns Element containing the question HTML, void if the data is not valid.
|
||||
*/
|
||||
initComponent(): void | HTMLElement {
|
||||
if (!this.question) {
|
||||
this.logger.warn('Aborting because of no question received.');
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
|
||||
this.hostElement.classList.add('core-question-container');
|
||||
|
||||
const element = CoreDomUtils.convertToElement(this.question.html);
|
||||
|
||||
// Extract question text.
|
||||
this.question.text = CoreDomUtils.getContentsOfElement(element, '.qtext');
|
||||
if (this.question.text === undefined) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.question.slot);
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a question component of type essay.
|
||||
*
|
||||
|
@ -409,15 +428,13 @@ export class CoreQuestionBaseComponent {
|
|||
*/
|
||||
initOriginalTextComponent(contentSelector: string): void | HTMLElement {
|
||||
if (!this.question) {
|
||||
this.logger.warn('Aborting because of no question received.');
|
||||
|
||||
return CoreQuestionHelper.showComponentError(this.onAbort);
|
||||
return;
|
||||
}
|
||||
|
||||
const element = CoreDomUtils.convertToElement(this.question.html);
|
||||
|
||||
// Get question content.
|
||||
const content = <HTMLElement> element.querySelector(contentSelector);
|
||||
const content = element.querySelector<HTMLElement>(contentSelector);
|
||||
if (!content) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.question.slot);
|
||||
|
||||
|
@ -473,15 +490,15 @@ export class CoreQuestionBaseComponent {
|
|||
if (input.classList.contains('incorrect')) {
|
||||
question.input.correctClass = 'core-question-incorrect';
|
||||
question.input.correctIcon = 'fas-times';
|
||||
question.input.correctIconColor = 'danger';
|
||||
question.input.correctIconColor = CoreIonicColorNames.DANGER;
|
||||
} else if (input.classList.contains('correct')) {
|
||||
question.input.correctClass = 'core-question-correct';
|
||||
question.input.correctIcon = 'fas-check';
|
||||
question.input.correctIconColor = 'success';
|
||||
question.input.correctIconColor = CoreIonicColorNames.SUCCESS;
|
||||
} else if (input.classList.contains('partiallycorrect')) {
|
||||
question.input.correctClass = 'core-question-partiallycorrect';
|
||||
question.input.correctIcon = 'fas-check-square';
|
||||
question.input.correctIconColor = 'warning';
|
||||
question.input.correctIconColor = CoreIonicColorNames.WARNING;
|
||||
} else {
|
||||
question.input.correctClass = '';
|
||||
question.input.correctIcon = '';
|
||||
|
|
Loading…
Reference in New Issue