diff --git a/src/addon/qtype/gapselect/component/gapselect.html b/src/addon/qtype/gapselect/component/gapselect.html
new file mode 100644
index 000000000..eaeb68661
--- /dev/null
+++ b/src/addon/qtype/gapselect/component/gapselect.html
@@ -0,0 +1,5 @@
+
diff --git a/src/addon/qtype/gapselect/component/gapselect.ts b/src/addon/qtype/gapselect/component/gapselect.ts
new file mode 100644
index 000000000..70a4622cf
--- /dev/null
+++ b/src/addon/qtype/gapselect/component/gapselect.ts
@@ -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');
+ }
+}
diff --git a/src/addon/qtype/gapselect/gapselect.module.ts b/src/addon/qtype/gapselect/gapselect.module.ts
new file mode 100644
index 000000000..f95b7b790
--- /dev/null
+++ b/src/addon/qtype/gapselect/gapselect.module.ts
@@ -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);
+ }
+}
diff --git a/src/addon/qtype/gapselect/providers/handler.ts b/src/addon/qtype/gapselect/providers/handler.ts
new file mode 100644
index 000000000..e18b357cd
--- /dev/null
+++ b/src/addon/qtype/gapselect/providers/handler.ts
@@ -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} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, question: any): any | Promise {
+ 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} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ 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);
+ }
+}
diff --git a/src/addon/qtype/multianswer/component/multianswer.html b/src/addon/qtype/multianswer/component/multianswer.html
new file mode 100644
index 000000000..ae07a5f43
--- /dev/null
+++ b/src/addon/qtype/multianswer/component/multianswer.html
@@ -0,0 +1,5 @@
+
diff --git a/src/addon/qtype/multianswer/component/multianswer.ts b/src/addon/qtype/multianswer/component/multianswer.ts
new file mode 100644
index 000000000..79cdba492
--- /dev/null
+++ b/src/addon/qtype/multianswer/component/multianswer.ts
@@ -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');
+ }
+}
diff --git a/src/addon/qtype/multianswer/multianswer.module.ts b/src/addon/qtype/multianswer/multianswer.module.ts
new file mode 100644
index 000000000..b1c37c051
--- /dev/null
+++ b/src/addon/qtype/multianswer/multianswer.module.ts
@@ -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);
+ }
+}
diff --git a/src/addon/qtype/multianswer/providers/handler.ts b/src/addon/qtype/multianswer/providers/handler.ts
new file mode 100644
index 000000000..b02a4eb70
--- /dev/null
+++ b/src/addon/qtype/multianswer/providers/handler.ts
@@ -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} The component (or promise resolved with component) to use, undefined if not found.
+ */
+ getComponent(injector: Injector, question: any): any | Promise {
+ 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} True or promise resolved with true if enabled.
+ */
+ isEnabled(): boolean | Promise {
+ 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;
+ }
+}
diff --git a/src/addon/qtype/qtype.module.ts b/src/addon/qtype/qtype.module.ts
index 96df1cebb..a8563022c 100644
--- a/src/addon/qtype/qtype.module.ts
+++ b/src/addon/qtype/qtype.module.ts
@@ -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,
diff --git a/src/core/question/classes/base-question-component.ts b/src/core/question/classes/base-question-component.ts
index 6daac231d..5befc6391 100644
--- a/src/core/question/classes/base-question-component.ts
+++ b/src/core/question/classes/base-question-component.ts
@@ -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 = 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".
*
diff --git a/src/core/question/providers/helper.ts b/src/core/question/providers/helper.ts
index 61b67c5a8..f6d317dd2 100644
--- a/src/core/question/providers/helper.ts
+++ b/src/core/question/providers/helper.ts
@@ -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 = 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 = 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);
+ });
+ }
+ });
+ }
}