MOBILE-2390 qtype: Implement ddwtos type

main
Dani Palou 2018-03-21 12:19:41 +01:00
parent cd68db376a
commit d14d700f7b
6 changed files with 826 additions and 0 deletions

View File

@ -0,0 +1,550 @@
// (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 { CoreLoggerProvider } from '@providers/logger';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
/**
* Set of functions to get the CSS selectors.
*/
export interface AddonQtypeDdwtosQuestionCSSSelectors {
topNode?: () => string;
dragContainer?: () => string;
drags?: () => string;
drag?: (no: number) => string;
dragsInGroup?: (groupNo: number) => string;
unplacedDragsInGroup?: (groupNo: number) => string;
dragsForChoiceInGroup?: (choiceNo: number, groupNo: number) => string;
unplacedDragsForChoiceInGroup?: (choiceNo: number, groupNo: number) => string;
drops?: () => string;
dropForPlace?: (placeNo: number) => string;
dropsInGroup?: (groupNo: number) => string;
dragHomes?: () => string;
dragHomesGroup?: (groupNo: number) => string;
dragHome?: (groupNo: number, choiceNo: number) => string;
dropsGroup?: (groupNo: number) => string;
}
/**
* Class to make a question of ddwtos type work.
*/
export class AddonQtypeDdwtosQuestion {
protected logger: any;
protected nextDragItemNo = 1;
protected selectors: AddonQtypeDdwtosQuestionCSSSelectors; // Result of cssSelectors.
protected placed: {[no: number]: number}; // Map that relates drag elements numbers with drop zones numbers.
protected selected: HTMLElement; // Selected element (being "dragged").
/**
* Create the instance.
*
* @param {CoreLoggerProvider} logger Logger provider.
* @param {CoreDomUtilsProvider} domUtils Dom Utils provider.
* @param {HTMLElement} container The container HTMLElement of the question.
* @param {any} question The question instance.
* @param {boolean} readOnly Whether it's read only.
* @param {string[]} inputIds Ids of the inputs of the question (where the answers will be stored).
*/
constructor(logger: CoreLoggerProvider, protected domUtils: CoreDomUtilsProvider, protected container: HTMLElement,
protected question: any, protected readOnly: boolean, protected inputIds: string[]) {
this.logger = logger.getInstance('AddonQtypeDdwtosQuestion');
this.initializer(question);
}
/**
* Clone a drag item and add it to the drag container.
*
* @param {HTMLElement} dragHome Item to clone
*/
cloneDragItem(dragHome: HTMLElement): void {
const drag = <HTMLElement> dragHome.cloneNode(true);
drag.classList.remove('draghome');
drag.classList.add('drag');
drag.classList.add('no' + this.nextDragItemNo);
this.nextDragItemNo++;
drag.style.visibility = 'visible';
drag.style.position = 'absolute';
const container = this.container.querySelector(this.selectors.dragContainer());
container.appendChild(drag);
if (!this.readOnly) {
this.makeDraggable(drag);
}
}
/**
* Clone the 'drag homes'.
* Invisible 'drag homes' are output in the question. These have the same properties as the drag items but are invisible.
* We clone these invisible elements to make the actual drag items.
*/
cloneDragItems(): void {
const dragHomes = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.dragHomes()));
for (let x = 0; x < dragHomes.length; x++) {
this.cloneDragItemsForOneChoice(dragHomes[x]);
}
}
/**
* Clone a certain 'drag home'. If it's an "infinite" drag, clone it several times.
*
* @param {HTMLElement} dragHome Element to clone.
*/
cloneDragItemsForOneChoice(dragHome: HTMLElement): void {
if (dragHome.classList.contains('infinite')) {
const groupNo = this.getGroup(dragHome),
noOfDrags = this.container.querySelectorAll(this.selectors.dropsInGroup(groupNo)).length;
for (let x = 0; x < noOfDrags; x++) {
this.cloneDragItem(dragHome);
}
} else {
this.cloneDragItem(dragHome);
}
}
/**
* Get an object with a set of functions to get the CSS selectors.
*
* @param {number} slot Question slot.
* @return {AddonQtypeDdwtosQuestionCSSSelectors} Object with the functions to get the selectors.
*/
cssSelectors(slot: number): AddonQtypeDdwtosQuestionCSSSelectors {
const topNode = '#core-question-' + slot + ' .addon-qtype-ddwtos-container',
selectors: AddonQtypeDdwtosQuestionCSSSelectors = {};
selectors.topNode = (): string => {
return topNode;
};
selectors.dragContainer = (): string => {
return topNode + ' div.drags';
};
selectors.drags = (): string => {
return selectors.dragContainer() + ' span.drag';
};
selectors.drag = (no: number): string => {
return selectors.drags() + '.no' + no;
};
selectors.dragsInGroup = (groupNo: number): string => {
return selectors.drags() + '.group' + groupNo;
};
selectors.unplacedDragsInGroup = (groupNo: number): string => {
return selectors.dragsInGroup(groupNo) + '.unplaced';
};
selectors.dragsForChoiceInGroup = (choiceNo: number, groupNo: number): string => {
return selectors.dragsInGroup(groupNo) + '.choice' + choiceNo;
};
selectors.unplacedDragsForChoiceInGroup = (choiceNo: number, groupNo: number): string => {
return selectors.unplacedDragsInGroup(groupNo) + '.choice' + choiceNo;
};
selectors.drops = (): string => {
return topNode + ' span.drop';
};
selectors.dropForPlace = (placeNo: number): string => {
return selectors.drops() + '.place' + placeNo;
};
selectors.dropsInGroup = (groupNo: number): string => {
return selectors.drops() + '.group' + groupNo;
};
selectors.dragHomes = (): string => {
return topNode + ' span.draghome';
};
selectors.dragHomesGroup = (groupNo: number): string => {
return topNode + ' .draggrouphomes' + groupNo + ' span.draghome';
};
selectors.dragHome = (groupNo: number, choiceNo: number): string => {
return topNode + ' .draggrouphomes' + groupNo + ' span.draghome.choice' + choiceNo;
};
selectors.dropsGroup = (groupNo: number): string => {
return topNode + ' span.drop.group' + groupNo;
};
return selectors;
}
/**
* Deselect all drags.
*/
deselectDrags(): void {
// Remove the selected class from all drags.
const drags = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drags()));
drags.forEach((drag) => {
drag.classList.remove('selected');
});
this.selected = null;
}
/**
* Function to call when the instance is no longer needed.
*/
destroy(): void {
window.removeEventListener('resize', this.resizeFunction);
}
/**
* Get the choice number of an element. It is extracted from the classes.
*
* @param {HTMLElement} node Element to check.
* @return {number} Choice number.
*/
getChoice(node: HTMLElement): number {
return this.getClassnameNumericSuffix(node, 'choice');
}
/**
* Get the number in a certain class name of an element.
*
* @param {HTMLElement} node The element to check.
* @param {string} prefix Prefix of the class to check.
* @return {number} The number in the class.
*/
getClassnameNumericSuffix(node: HTMLElement, prefix: string): number {
if (node.classList && node.classList.length) {
const patt1 = new RegExp('^' + prefix + '([0-9])+$'),
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]);
return Number(match[0]);
}
}
}
this.logger.warn('Prefix "' + prefix + '" not found in class names.');
}
/**
* Get the group number of an element. It is extracted from the classes.
*
* @param {HTMLElement} node Element to check.
* @return {number} Group number.
*/
getGroup(node: HTMLElement): number {
return this.getClassnameNumericSuffix(node, 'group');
}
/**
* Get the number of an element ('no'). It is extracted from the classes.
*
* @param {HTMLElement} node Element to check.
* @return {number} Number.
*/
getNo(node: HTMLElement): number {
return this.getClassnameNumericSuffix(node, 'no');
}
/**
* Get the place number of an element. It is extracted from the classes.
*
* @param {HTMLElement} node Element to check.
* @return {number} Place number.
*/
getPlace(node: HTMLElement): number {
return this.getClassnameNumericSuffix(node, 'place');
}
/**
* Initialize the question.
*
* @param {any} question Question.
*/
initializer(question: any): void {
this.selectors = this.cssSelectors(question.slot);
const container = <HTMLElement> this.container.querySelector(this.selectors.topNode());
if (this.readOnly) {
container.classList.add('readonly');
} else {
container.classList.add('notreadonly');
}
this.setPaddingSizesAll();
this.cloneDragItems();
this.initialPlaceOfDragItems();
this.makeDropZones();
// Wait the DOM to be rendered.
setTimeout(() => {
this.positionDragItems();
});
window.addEventListener('resize', this.resizeFunction);
}
/**
* Initialize drag items, putting them in their initial place.
*/
initialPlaceOfDragItems(): void {
const drags = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drags()));
// Add the class 'unplaced' to all elements.
drags.forEach((drag) => {
drag.classList.add('unplaced');
});
this.placed = {};
for (const placeNo in this.inputIds) {
const inputId = this.inputIds[placeNo],
inputNode = this.container.querySelector('input#' + inputId),
choiceNo = Number(inputNode.getAttribute('value'));
if (choiceNo !== 0) {
const drop = <HTMLElement> this.container.querySelector(this.selectors.dropForPlace(parseInt(placeNo, 10) + 1)),
groupNo = this.getGroup(drop),
drag = <HTMLElement> this.container.querySelector(
this.selectors.unplacedDragsForChoiceInGroup(choiceNo, groupNo));
this.placeDragInDrop(drag, drop);
this.positionDragItem(drag);
}
}
}
/**
* Make an element "draggable". In the mobile app, items are "dragged" using tap and drop.
*
* @param {HTMLElement} drag Element.
*/
makeDraggable(drag: HTMLElement): void {
drag.addEventListener('click', () => {
if (drag.classList.contains('selected')) {
this.deselectDrags();
} else {
this.selectDrag(drag);
}
});
}
/**
* Convert an element into a drop zone.
*
* @param {HTMLElement} drop Element.
*/
makeDropZone(drop: HTMLElement): void {
drop.addEventListener('click', () => {
const drag = this.selected;
if (!drag) {
// No element selected, nothing to do.
return false;
}
// Place it only if the same group is selected.
if (this.getGroup(drag) === this.getGroup(drop)) {
this.placeDragInDrop(drag, drop);
this.deselectDrags();
this.positionDragItem(drag);
}
});
}
/**
* Create all drop zones.
*/
makeDropZones(): void {
if (this.readOnly) {
return;
}
// Create all the drop zones.
const drops = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drops()));
drops.forEach((drop) => {
this.makeDropZone(drop);
});
// If home answer zone is clicked, return drag home.
const home = <HTMLElement> this.container.querySelector(this.selectors.topNode() + ' .answercontainer');
home.addEventListener('click', () => {
const drag = this.selected;
if (!drag) {
// No element selected, nothing to do.
return;
}
// Not placed yet, deselect.
if (drag.classList.contains('unplaced')) {
this.deselectDrags();
return;
}
// Remove, deselect and move back home in this order.
this.removeDragFromDrop(drag);
this.deselectDrags();
this.positionDragItem(drag);
});
}
/**
* Set the width and height of an element.
*
* @param {HTMLElement} node Element.
* @param {number} width Width to set.
* @param {number} height Height to set.
*/
protected padToWidthHeight(node: HTMLElement, width: number, height: number): void {
node.style.width = width + 'px';
node.style.height = height + 'px';
node.style.lineHeight = height + 'px';
}
/**
* Place a draggable element inside a drop zone.
*
* @param {HTMLElement} drag Draggable element.
* @param {HTMLElement} drop Drop zone.
*/
placeDragInDrop(drag: HTMLElement, drop: HTMLElement): void {
const placeNo = this.getPlace(drop),
inputId = this.inputIds[placeNo - 1],
inputNode = this.container.querySelector('input#' + inputId);
// Set the value of the drag element in the input of the drop zone.
if (drag !== null) {
inputNode.setAttribute('value', String(this.getChoice(drag)));
} else {
inputNode.setAttribute('value', '0');
}
// Remove the element from the "placed" map if it's there.
for (const alreadyThereDragNo in this.placed) {
if (this.placed[alreadyThereDragNo] === placeNo) {
delete this.placed[alreadyThereDragNo];
}
}
if (drag !== null) {
// Add the element in the "placed" map.
this.placed[this.getNo(drag)] = placeNo;
}
}
/**
* Position a drag element in the right drop zone or in the home zone.
*
* @param {HTMLElement} drag Drag element.
*/
positionDragItem(drag: HTMLElement): void {
let position;
const placeNo = this.placed[this.getNo(drag)];
if (!placeNo) {
// Not placed, put it in home zone.
const groupNo = this.getGroup(drag),
choiceNo = this.getChoice(drag);
position = this.domUtils.getElementXY(this.container, this.selectors.dragHome(groupNo, choiceNo), 'answercontainer');
drag.classList.add('unplaced');
} else {
// Get the drop zone position.
position = this.domUtils.getElementXY(this.container, this.selectors.dropForPlace(placeNo),
'addon-qtype-ddwtos-container');
drag.classList.remove('unplaced');
}
if (position) {
drag.style.left = position[0] + 'px';
drag.style.top = position[1] + 'px';
}
}
/**
* Postition, or reposition, all the drag items. They're placed in the right drop zone or in the home zone.
*/
positionDragItems(): void {
const drags = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drags()));
drags.forEach((drag) => {
this.positionDragItem(drag);
});
}
/**
* Remove a draggable element from a drop zone.
*
* @param {HTMLElement} drag The draggable element.
*/
removeDragFromDrop(drag: HTMLElement): void {
const placeNo = this.placed[this.getNo(drag)],
drop = <HTMLElement> this.container.querySelector(this.selectors.dropForPlace(placeNo));
this.placeDragInDrop(null, drop);
}
/**
* Function to call when the window is resized.
*/
resizeFunction(): void {
this.positionDragItems();
}
/**
* Select a certain element as being "dragged".
*
* @param {HTMLElement} drag Element.
*/
selectDrag(drag: HTMLElement): void {
// Deselect previous drags, only 1 can be selected.
this.deselectDrags();
this.selected = drag;
drag.classList.add('selected');
}
/**
* Set the padding size for all groups.
*/
setPaddingSizesAll(): void {
for (let groupNo = 1; groupNo <= 8; groupNo++) {
this.setPaddingSizeForGroup(groupNo);
}
}
/**
* Set the padding size for a certain group.
*
* @param {number} groupNo Group number.
*/
setPaddingSizeForGroup(groupNo: number): void {
const groupItems = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.dragHomesGroup(groupNo)));
if (groupItems.length !== 0) {
let maxWidth = 0,
maxHeight = 0;
// Find max height and width.
groupItems.forEach((item) => {
maxWidth = Math.max(maxWidth, Math.ceil(item.offsetWidth));
maxHeight = Math.max(maxHeight, Math.ceil(item.offsetHeight));
});
maxWidth += 8;
maxHeight += 2;
groupItems.forEach((item) => {
this.padToWidthHeight(item, maxWidth, maxHeight);
});
const dropsGroup = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.dropsGroup(groupNo)));
dropsGroup.forEach((item) => {
this.padToWidthHeight(item, maxWidth + 2, maxHeight + 2);
});
}
}
}

View File

@ -0,0 +1,11 @@
<section ion-list *ngIf="question.text || question.text === ''" class="addon-qtype-ddwtos-container">
<ion-item text-wrap>
<p *ngIf="!question.readOnly" class="core-info-card-icon">
<ion-icon name="information" item-start></ion-icon>
{{ 'core.question.howtodraganddrop' | translate }}
</p>
<p><core-format-text [component]="component" [componentId]="componentId" [text]="question.text"></core-format-text></p>
<core-format-text *ngIf="question.answers" [component]="component" [componentId]="componentId" [text]="question.answers"></core-format-text>
<div class="drags"></div>
</ion-item>
</section>

View File

@ -0,0 +1,101 @@
// (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, OnDestroy, AfterViewInit, Injector, ElementRef } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
import { AddonQtypeDdwtosQuestion } from '../classes/ddwtos';
/**
* Component to render a drag-and-drop words into sentences question.
*/
@Component({
selector: 'addon-qtype-ddwtos',
templateUrl: 'ddwtos.html'
})
export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent implements OnInit, AfterViewInit, OnDestroy {
protected element: HTMLElement;
protected questionInstance: AddonQtypeDdwtosQuestion;
protected inputIds: string[]; // Ids of the inputs of the question (where the answers will be stored).
constructor(logger: CoreLoggerProvider, injector: Injector, element: ElementRef) {
super(logger, 'AddonQtypeDdwtosComponent', injector);
this.element = element.nativeElement;
}
/**
* Component being initialized.
*/
ngOnInit(): void {
if (!this.question) {
this.logger.warn('Aborting because of no question received.');
return this.questionHelper.showComponentError(this.onAbort);
}
const div = document.createElement('div');
div.innerHTML = this.question.html;
// Replace Moodle's correct/incorrect and feedback classes with our own.
this.questionHelper.replaceCorrectnessClasses(div);
this.questionHelper.replaceFeedbackClasses(div);
// Treat the correct/incorrect icons.
this.questionHelper.treatCorrectnessIcons(div, this.component, this.componentId);
const answerContainer = div.querySelector('.answercontainer');
if (!answerContainer) {
this.logger.warn('Aborting because of an error parsing question.', this.question.name);
return this.questionHelper.showComponentError(this.onAbort);
}
this.question.readOnly = answerContainer.classList.contains('readonly');
this.question.answers = answerContainer.outerHTML;
this.question.text = this.domUtils.getContentsOfElement(div, '.qtext');
if (typeof this.question.text == 'undefined') {
this.logger.warn('Aborting because of an error parsing question.', this.question.name);
return this.questionHelper.showComponentError(this.onAbort);
}
// Get the inputs where the answers will be stored and add them to the question text.
const inputEls = <HTMLElement[]> Array.from(div.querySelectorAll('input[type="hidden"]:not([name*=sequencecheck])')),
inputIds = [];
inputEls.forEach((inputEl) => {
this.question.text += inputEl.outerHTML;
inputIds.push(inputEl.getAttribute('id'));
});
}
/**
* View has been initialized.
*/
ngAfterViewInit(): void {
// Create the instance.
this.questionInstance = new AddonQtypeDdwtosQuestion(this.logger, this.domUtils, this.element, this.question,
this.question.readOnly, this.inputIds);
}
/**
* Component being destroyed.
*/
ngOnDestroy(): void {
this.questionInstance && this.questionInstance.destroy();
}
}

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 { AddonQtypeDdwtosHandler } from './providers/handler';
import { AddonQtypeDdwtosComponent } from './component/ddwtos';
@NgModule({
declarations: [
AddonQtypeDdwtosComponent
],
imports: [
IonicModule,
TranslateModule.forChild(),
CoreDirectivesModule
],
providers: [
AddonQtypeDdwtosHandler
],
exports: [
AddonQtypeDdwtosComponent
],
entryComponents: [
AddonQtypeDdwtosComponent
]
})
export class AddonQtypeDdwtosModule {
constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeDdwtosHandler) {
questionDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,116 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionProvider } from '@core/question/providers/question';
import { CoreQuestionHandler } from '@core/question/providers/delegate';
import { AddonQtypeDdwtosComponent } from '../component/ddwtos';
/**
* Handler to support drag-and-drop words into sentences question type.
*/
@Injectable()
export class AddonQtypeDdwtosHandler implements CoreQuestionHandler {
name = 'AddonQtypeDdwtos';
type = 'qtype_ddwtos';
constructor(private questionProvider: CoreQuestionProvider) { }
/**
* 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 {
if (behaviour === 'interactive') {
return 'interactivecountback';
}
return behaviour;
}
/**
* 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 AddonQtypeDdwtosComponent;
}
/**
* 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 {
for (const name in answers) {
const value = answers[name];
if (!value || value === '0') {
return 0;
}
}
return 1;
}
/**
* 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 {
for (const name in answers) {
const value = answers[name];
if (value && value !== '0') {
return 1;
}
}
return 0;
}
/**
* 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.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
}
}

View File

@ -16,6 +16,7 @@ import { NgModule } from '@angular/core';
import { AddonQtypeCalculatedModule } from './calculated/calculated.module';
import { AddonQtypeCalculatedMultiModule } from './calculatedmulti/calculatedmulti.module';
import { AddonQtypeCalculatedSimpleModule } from './calculatedsimple/calculatedsimple.module';
import { AddonQtypeDdwtosModule } from './ddwtos/ddwtos.module';
import { AddonQtypeDescriptionModule } from './description/description.module';
import { AddonQtypeEssayModule } from './essay/essay.module';
import { AddonQtypeGapSelectModule } from './gapselect/gapselect.module';
@ -33,6 +34,7 @@ import { AddonQtypeTrueFalseModule } from './truefalse/truefalse.module';
AddonQtypeCalculatedModule,
AddonQtypeCalculatedMultiModule,
AddonQtypeCalculatedSimpleModule,
AddonQtypeDdwtosModule,
AddonQtypeDescriptionModule,
AddonQtypeEssayModule,
AddonQtypeGapSelectModule,