MOBILE-2389 qtype: Implement multianswer and gapselect types
parent
7041485359
commit
cd68db376a
|
@ -0,0 +1,5 @@
|
|||
<section ion-list class="addon-qtype-gapselect-container" *ngIf="question.text || question.text === ''">
|
||||
<ion-item text-wrap>
|
||||
<p><core-format-text [component]="component" [componentId]="componentId" [text]="question.text"></core-format-text></p>
|
||||
</ion-item>
|
||||
</section>
|
|
@ -0,0 +1,38 @@
|
|||
// (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, Injector } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
* Component to render a gap select question.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-qtype-gapselect',
|
||||
templateUrl: 'gapselect.html'
|
||||
})
|
||||
export class AddonQtypeGapSelectComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, injector: Injector) {
|
||||
super(logger, 'AddonQtypeGapSelectComponent', injector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.initOriginalTextComponent('.qtext');
|
||||
}
|
||||
}
|
|
@ -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 { AddonQtypeGapSelectHandler } from './providers/handler';
|
||||
import { AddonQtypeGapSelectComponent } from './component/gapselect';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonQtypeGapSelectComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreDirectivesModule
|
||||
],
|
||||
providers: [
|
||||
AddonQtypeGapSelectHandler
|
||||
],
|
||||
exports: [
|
||||
AddonQtypeGapSelectComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonQtypeGapSelectComponent
|
||||
]
|
||||
})
|
||||
export class AddonQtypeGapSelectModule {
|
||||
constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeGapSelectHandler) {
|
||||
questionDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
|
||||
// (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 { AddonQtypeGapSelectComponent } from '../component/gapselect';
|
||||
|
||||
/**
|
||||
* Handler to support gapselect question type.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonQtypeGapSelectHandler implements CoreQuestionHandler {
|
||||
name = 'AddonQtypeGapSelect';
|
||||
type = 'qtype_gapselect';
|
||||
|
||||
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 AddonQtypeGapSelectComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
// We should always get a value for each select so we can assume we receive all the possible answers.
|
||||
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 {
|
||||
// We should always get a value for each select so we can assume we receive all the possible answers.
|
||||
for (const name in answers) {
|
||||
const value = answers[name];
|
||||
if (value) {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<section ion-list class="addon-qtype-multianswer-container" *ngIf="question.text || question.text === ''">
|
||||
<ion-item text-wrap>
|
||||
<p><core-format-text [component]="component" [componentId]="componentId" [text]="question.text"></core-format-text></p>
|
||||
</ion-item>
|
||||
</section>
|
|
@ -0,0 +1,38 @@
|
|||
// (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, Injector } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||
|
||||
/**
|
||||
* Component to render a multianswer question.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-qtype-multianswer',
|
||||
templateUrl: 'multianswer.html'
|
||||
})
|
||||
export class AddonQtypeMultiAnswerComponent extends CoreQuestionBaseComponent implements OnInit {
|
||||
|
||||
constructor(logger: CoreLoggerProvider, injector: Injector) {
|
||||
super(logger, 'AddonQtypeMultiAnswerComponent', injector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.initOriginalTextComponent('.formulation');
|
||||
}
|
||||
}
|
|
@ -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 { AddonQtypeMultiAnswerHandler } from './providers/handler';
|
||||
import { AddonQtypeMultiAnswerComponent } from './component/multianswer';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonQtypeMultiAnswerComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreDirectivesModule
|
||||
],
|
||||
providers: [
|
||||
AddonQtypeMultiAnswerHandler
|
||||
],
|
||||
exports: [
|
||||
AddonQtypeMultiAnswerComponent
|
||||
],
|
||||
entryComponents: [
|
||||
AddonQtypeMultiAnswerComponent
|
||||
]
|
||||
})
|
||||
export class AddonQtypeMultiAnswerModule {
|
||||
constructor(questionDelegate: CoreQuestionDelegate, handler: AddonQtypeMultiAnswerHandler) {
|
||||
questionDelegate.registerHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
|
||||
// (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 { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||
import { AddonQtypeMultiAnswerComponent } from '../component/multianswer';
|
||||
|
||||
/**
|
||||
* Handler to support multianswer question type.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler {
|
||||
name = 'AddonQtypeMultiAnswer';
|
||||
type = 'qtype_multianswer';
|
||||
|
||||
constructor(private questionProvider: CoreQuestionProvider, private questionHelper: CoreQuestionHelperProvider) { }
|
||||
|
||||
/**
|
||||
* 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 AddonQtypeMultiAnswerComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
// Get all the inputs in the question to check if they've all been answered.
|
||||
const names = this.questionProvider.getBasicAnswers(this.questionHelper.getAllInputNamesFromHtml(question.html));
|
||||
for (const name in names) {
|
||||
const value = answers[name];
|
||||
if (!value && value !== false && 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 {
|
||||
// We should always get a value for each select so we can assume we receive all the possible answers.
|
||||
for (const name in answers) {
|
||||
const value = answers[name];
|
||||
if (value || value === false) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if an offline sequencecheck is valid compared with the online one.
|
||||
* This function only needs to be implemented if a specific compare is required.
|
||||
*
|
||||
* @param {any} question The question.
|
||||
* @param {string} offlineSequenceCheck Sequence check stored in offline.
|
||||
* @return {boolean} Whether sequencecheck is valid.
|
||||
*/
|
||||
validateSequenceCheck(question: any, offlineSequenceCheck: string): boolean {
|
||||
if (question.sequencecheck == offlineSequenceCheck) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For some reason, viewing a multianswer for the first time without answering it creates a new step "todo".
|
||||
// We'll treat this case as valid.
|
||||
if (question.sequencecheck == 2 && question.state == 'todo' && offlineSequenceCheck == '1') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,9 @@ import { AddonQtypeCalculatedMultiModule } from './calculatedmulti/calculatedmul
|
|||
import { AddonQtypeCalculatedSimpleModule } from './calculatedsimple/calculatedsimple.module';
|
||||
import { AddonQtypeDescriptionModule } from './description/description.module';
|
||||
import { AddonQtypeEssayModule } from './essay/essay.module';
|
||||
import { AddonQtypeGapSelectModule } from './gapselect/gapselect.module';
|
||||
import { AddonQtypeMatchModule } from './match/match.module';
|
||||
import { AddonQtypeMultiAnswerModule } from './multianswer/multianswer.module';
|
||||
import { AddonQtypeMultichoiceModule } from './multichoice/multichoice.module';
|
||||
import { AddonQtypeNumericalModule } from './numerical/numerical.module';
|
||||
import { AddonQtypeRandomSaMatchModule } from './randomsamatch/randomsamatch.module';
|
||||
|
@ -33,7 +35,9 @@ import { AddonQtypeTrueFalseModule } from './truefalse/truefalse.module';
|
|||
AddonQtypeCalculatedSimpleModule,
|
||||
AddonQtypeDescriptionModule,
|
||||
AddonQtypeEssayModule,
|
||||
AddonQtypeGapSelectModule,
|
||||
AddonQtypeMatchModule,
|
||||
AddonQtypeMultiAnswerModule,
|
||||
AddonQtypeMultichoiceModule,
|
||||
AddonQtypeNumericalModule,
|
||||
AddonQtypeRandomSaMatchModule,
|
||||
|
|
|
@ -230,6 +230,45 @@ export class CoreQuestionBaseComponent {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a question component that uses the original question text with some basic treatment.
|
||||
*
|
||||
* @param {string} contentSelector The selector to find the question content (text).
|
||||
* @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid.
|
||||
*/
|
||||
initOriginalTextComponent(contentSelector: string): void | HTMLElement {
|
||||
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;
|
||||
|
||||
// Get question content.
|
||||
const content = <HTMLElement> div.querySelector(contentSelector);
|
||||
if (!content) {
|
||||
this.logger.warn('Aborting because of an error parsing question.', this.question.name);
|
||||
|
||||
return this.questionHelper.showComponentError(this.onAbort);
|
||||
}
|
||||
|
||||
// Remove sequencecheck and validation error.
|
||||
this.domUtils.removeElement(content, 'input[name*=sequencecheck]');
|
||||
this.domUtils.removeElement(content, '.validationerror');
|
||||
|
||||
// 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);
|
||||
|
||||
// Set the question text.
|
||||
this.question.text = content.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a question component that has an input of type "text".
|
||||
*
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable, EventEmitter } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
|
@ -27,7 +28,8 @@ export class CoreQuestionHelperProvider {
|
|||
protected div = document.createElement('div'); // A div element to search in HTML code.
|
||||
|
||||
constructor(private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider,
|
||||
private questionProvider: CoreQuestionProvider, private sitesProvider: CoreSitesProvider) { }
|
||||
private questionProvider: CoreQuestionProvider, private sitesProvider: CoreSitesProvider,
|
||||
private translate: TranslateService) { }
|
||||
|
||||
/**
|
||||
* Add a behaviour button to the question's "behaviourButtons" property.
|
||||
|
@ -267,6 +269,35 @@ export class CoreQuestionHelperProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the names of all the inputs inside an HTML code.
|
||||
* This function will return an object where the keys are the input names. The values will always be true.
|
||||
* This is in order to make this function compatible with other functions like CoreQuestionProvider.getBasicAnswers.
|
||||
*
|
||||
* @param {string} html HTML code.
|
||||
* @return {any} Object where the keys are the names.
|
||||
*/
|
||||
getAllInputNamesFromHtml(html: string): any {
|
||||
const form = document.createElement('form'),
|
||||
answers = {};
|
||||
|
||||
form.innerHTML = html;
|
||||
|
||||
// Search all input elements.
|
||||
Array.from(form.elements).forEach((element: HTMLInputElement) => {
|
||||
const name = element.name || '';
|
||||
|
||||
// Ignore flag and submit inputs.
|
||||
if (!name || name.match(/_:flagged$/) || element.type == 'submit' || element.tagName == 'BUTTON') {
|
||||
return;
|
||||
}
|
||||
|
||||
answers[this.questionProvider.removeQuestionPrefix(name)] = true;
|
||||
});
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an HTML code with list of attachments, returns the list of attached files (filename and fileurl).
|
||||
* Please take into account that this function will treat all the anchors in the HTML, you should provide
|
||||
|
@ -397,6 +428,30 @@ export class CoreQuestionHelperProvider {
|
|||
question.html = form.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace Moodle's correct/incorrect classes with the Mobile ones.
|
||||
*
|
||||
* @param {HTMLElement} element DOM element.
|
||||
*/
|
||||
replaceCorrectnessClasses(element: HTMLElement): void {
|
||||
this.domUtils.replaceClassesInElement(element, {
|
||||
correct: 'core-question-answer-correct',
|
||||
incorrect: 'core-question-answer-incorrect'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace Moodle's feedback classes with the Mobile ones.
|
||||
*
|
||||
* @param {HTMLElement} element DOM element.
|
||||
*/
|
||||
replaceFeedbackClasses(element: HTMLElement): void {
|
||||
this.domUtils.replaceClassesInElement(element, {
|
||||
outcome: 'core-question-feedback-container core-question-feedback-padding',
|
||||
specificfeedback: 'core-question-feedback-container core-question-feedback-inline'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search a behaviour button in a certain question property containing HTML.
|
||||
*
|
||||
|
@ -443,4 +498,55 @@ export class CoreQuestionHelperProvider {
|
|||
|
||||
onAbort && onAbort.emit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat correctness icons, replacing them with local icons and setting click events to show the feedback if needed.
|
||||
*
|
||||
* @param {HTMLElement} element DOM element.
|
||||
*/
|
||||
treatCorrectnessIcons(element: HTMLElement, component?: string, componentId?: number): void {
|
||||
|
||||
const icons = <HTMLImageElement[]> Array.from(element.querySelectorAll('img.icon, img.questioncorrectnessicon'));
|
||||
icons.forEach((icon) => {
|
||||
// Replace the icon with the font version.
|
||||
if (icon.src) {
|
||||
const newIcon: any = document.createElement('i');
|
||||
|
||||
if (icon.src.indexOf('incorrect') > -1) {
|
||||
newIcon.className = 'icon fa fa-remove text-danger fa-fw questioncorrectnessicon';
|
||||
} else if (icon.src.indexOf('correct') > -1) {
|
||||
newIcon.className = 'icon fa fa-check text-success fa-fw questioncorrectnessicon';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
newIcon.title = icon.title;
|
||||
newIcon.ariaLabel = icon.title;
|
||||
icon.parentNode.replaceChild(newIcon, icon);
|
||||
}
|
||||
});
|
||||
|
||||
const spans = Array.from(element.querySelectorAll('.feedbackspan.accesshide'));
|
||||
spans.forEach((span) => {
|
||||
// Search if there's a hidden feedback for this element.
|
||||
const icon = <HTMLElement> span.previousSibling;
|
||||
if (!icon) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!icon.classList.contains('icon') && !icon.classList.contains('questioncorrectnessicon')) {
|
||||
return;
|
||||
}
|
||||
|
||||
icon.classList.add('questioncorrectnessicon');
|
||||
|
||||
if (span.innerHTML) {
|
||||
// There's a hidden feedback, show it when the icon is clicked.
|
||||
icon.addEventListener('click', (event) => {
|
||||
const title = this.translate.instant('core.question.feedback');
|
||||
this.textUtils.expandText(title, span.innerHTML, component, componentId);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue