commit
						6485ded480
					
				| @ -27,8 +27,6 @@ import * as moment from 'moment'; | ||||
| @Injectable() | ||||
| export class AddonModLessonHelperProvider { | ||||
| 
 | ||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 | ||||
| 
 | ||||
|     constructor(private domUtils: CoreDomUtilsProvider, private fb: FormBuilder, private translate: TranslateService, | ||||
|             private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { } | ||||
| 
 | ||||
| @ -39,8 +37,9 @@ export class AddonModLessonHelperProvider { | ||||
|      * @return {{formatted: boolean, label: string, href: string}} Formatted data. | ||||
|      */ | ||||
|     formatActivityLink(activityLink: string): {formatted: boolean, label: string, href: string} { | ||||
|         this.div.innerHTML = activityLink; | ||||
|         const anchor = this.div.querySelector('a'); | ||||
|         const element = this.domUtils.convertToElement(activityLink), | ||||
|             anchor = element.querySelector('a'); | ||||
| 
 | ||||
|         if (!anchor) { | ||||
|             // Anchor not found, return the original HTML.
 | ||||
|             return { | ||||
| @ -67,11 +66,11 @@ export class AddonModLessonHelperProvider { | ||||
|         const data = { | ||||
|                 buttonText: '', | ||||
|                 content: '' | ||||
|             }; | ||||
|             }, | ||||
|             element = this.domUtils.convertToElement(html); | ||||
| 
 | ||||
|         // Search the input button.
 | ||||
|         this.div.innerHTML = html; | ||||
|         const button = <HTMLInputElement> this.div.querySelector('input[type="button"]'); | ||||
|         const button = <HTMLInputElement> element.querySelector('input[type="button"]'); | ||||
| 
 | ||||
|         if (button) { | ||||
|             // Extract the button content and remove it from the HTML.
 | ||||
| @ -79,7 +78,7 @@ export class AddonModLessonHelperProvider { | ||||
|             button.remove(); | ||||
|         } | ||||
| 
 | ||||
|         data.content = this.div.innerHTML.trim(); | ||||
|         data.content = element.innerHTML.trim(); | ||||
| 
 | ||||
|         return data; | ||||
|     } | ||||
| @ -91,19 +90,19 @@ export class AddonModLessonHelperProvider { | ||||
|      * @return {any[]} List of buttons. | ||||
|      */ | ||||
|     getPageButtonsFromHtml(html: string): any[] { | ||||
|         const buttons = []; | ||||
|         const buttons = [], | ||||
|             element = this.domUtils.convertToElement(html); | ||||
| 
 | ||||
|         // Get the container of the buttons if it exists.
 | ||||
|         this.div.innerHTML = html; | ||||
|         let buttonsContainer = this.div.querySelector('.branchbuttoncontainer'); | ||||
|         let buttonsContainer = element.querySelector('.branchbuttoncontainer'); | ||||
| 
 | ||||
|         if (!buttonsContainer) { | ||||
|             // Button container not found, might be a legacy lesson (from 1.9).
 | ||||
|             if (!this.div.querySelector('form input[type="submit"]')) { | ||||
|             if (!element.querySelector('form input[type="submit"]')) { | ||||
|                 // No buttons found.
 | ||||
|                 return buttons; | ||||
|             } | ||||
|             buttonsContainer = this.div; | ||||
|             buttonsContainer = element; | ||||
|         } | ||||
| 
 | ||||
|         const forms = Array.from(buttonsContainer.querySelectorAll('form')); | ||||
| @ -144,8 +143,8 @@ export class AddonModLessonHelperProvider { | ||||
|      */ | ||||
|     getPageContentsFromPageData(data: any): string { | ||||
|         // Search the page contents inside the whole page HTML. Use data.pagecontent because it's filtered.
 | ||||
|         this.div.innerHTML = data.pagecontent; | ||||
|         const contents = this.div.querySelector('.contents'); | ||||
|         const element = this.domUtils.convertToElement(data.pagecontent), | ||||
|             contents = element.querySelector('.contents'); | ||||
| 
 | ||||
|         if (contents) { | ||||
|             return contents.innerHTML.trim(); | ||||
| @ -163,20 +162,20 @@ export class AddonModLessonHelperProvider { | ||||
|      * @return {any} Question data. | ||||
|      */ | ||||
|     getQuestionFromPageData(questionForm: FormGroup, pageData: any): any { | ||||
|         const question: any = {}; | ||||
|         const question: any = {}, | ||||
|             element = this.domUtils.convertToElement(pageData.pagecontent); | ||||
| 
 | ||||
|         // Get the container of the question answers if it exists.
 | ||||
|         this.div.innerHTML = pageData.pagecontent; | ||||
|         const fieldContainer = this.div.querySelector('.fcontainer'); | ||||
|         const fieldContainer = element.querySelector('.fcontainer'); | ||||
| 
 | ||||
|         // Get hidden inputs and add their data to the form group.
 | ||||
|         const hiddenInputs = <HTMLInputElement[]> Array.from(this.div.querySelectorAll('input[type="hidden"]')); | ||||
|         const hiddenInputs = <HTMLInputElement[]> Array.from(element.querySelectorAll('input[type="hidden"]')); | ||||
|         hiddenInputs.forEach((input) => { | ||||
|             questionForm.addControl(input.name, this.fb.control(input.value)); | ||||
|         }); | ||||
| 
 | ||||
|         // Get the submit button and extract its value.
 | ||||
|         const submitButton = <HTMLInputElement> this.div.querySelector('input[type="submit"]'); | ||||
|         const submitButton = <HTMLInputElement> element.querySelector('input[type="submit"]'); | ||||
|         question.submitLabel = submitButton ? submitButton.value : this.translate.instant('addon.mod_lesson.submit'); | ||||
| 
 | ||||
|         if (!fieldContainer) { | ||||
| @ -358,28 +357,27 @@ export class AddonModLessonHelperProvider { | ||||
|      *               the HTML. | ||||
|      */ | ||||
|     getQuestionPageAnswerDataFromHtml(html: string): any { | ||||
|         const data: any = {}; | ||||
| 
 | ||||
|         this.div.innerHTML = html; | ||||
|         const data: any = {}, | ||||
|             element = this.domUtils.convertToElement(html); | ||||
| 
 | ||||
|         // Check if it has a checkbox.
 | ||||
|         let input = <HTMLInputElement> this.div.querySelector('input[type="checkbox"][name*="answer"]'); | ||||
|         let input = <HTMLInputElement> element.querySelector('input[type="checkbox"][name*="answer"]'); | ||||
| 
 | ||||
|         if (input) { | ||||
|             // Truefalse or multichoice.
 | ||||
|             data.isCheckbox = true; | ||||
|             data.checked = !!input.checked; | ||||
|             data.name = input.name; | ||||
|             data.highlight = !!this.div.querySelector('.highlight'); | ||||
|             data.highlight = !!element.querySelector('.highlight'); | ||||
| 
 | ||||
|             input.remove(); | ||||
|             data.content = this.div.innerHTML.trim(); | ||||
|             data.content = element.innerHTML.trim(); | ||||
| 
 | ||||
|             return data; | ||||
|         } | ||||
| 
 | ||||
|         // Check if it has an input text or number.
 | ||||
|         input = <HTMLInputElement> this.div.querySelector('input[type="number"],input[type="text"]'); | ||||
|         input = <HTMLInputElement> element.querySelector('input[type="number"],input[type="text"]'); | ||||
|         if (input) { | ||||
|             // Short answer or numeric.
 | ||||
|             data.isText = true; | ||||
| @ -389,7 +387,7 @@ export class AddonModLessonHelperProvider { | ||||
|         } | ||||
| 
 | ||||
|         // Check if it has a select.
 | ||||
|         const select = <HTMLSelectElement> this.div.querySelector('select'); | ||||
|         const select = <HTMLSelectElement> element.querySelector('select'); | ||||
|         if (select && select.options) { | ||||
|             // Matching.
 | ||||
|             const selectedOption = select.options[select.selectedIndex]; | ||||
| @ -402,7 +400,7 @@ export class AddonModLessonHelperProvider { | ||||
|             } | ||||
| 
 | ||||
|             select.remove(); | ||||
|             data.content = this.div.innerHTML.trim(); | ||||
|             data.content = element.innerHTML.trim(); | ||||
| 
 | ||||
|             return data; | ||||
|         } | ||||
| @ -477,11 +475,11 @@ export class AddonModLessonHelperProvider { | ||||
|      * @return {string} Feedback without the question text. | ||||
|      */ | ||||
|     removeQuestionFromFeedback(html: string): string { | ||||
|         this.div.innerHTML = html; | ||||
|         const element = this.domUtils.convertToElement(html); | ||||
| 
 | ||||
|         // Remove the question text.
 | ||||
|         this.domUtils.removeElement(this.div, '.generalbox:not(.feedback):not(.correctanswer)'); | ||||
|         this.domUtils.removeElement(element, '.generalbox:not(.feedback):not(.correctanswer)'); | ||||
| 
 | ||||
|         return this.div.innerHTML.trim(); | ||||
|         return element.innerHTML.trim(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreGradesProvider } from '@core/grades/providers/grades'; | ||||
| import { CoreSiteWSPreSets } from '@classes/site'; | ||||
| @ -170,10 +171,9 @@ export class AddonModLessonProvider { | ||||
| 
 | ||||
|     protected ROOT_CACHE_KEY = 'mmaModLesson:'; | ||||
|     protected logger; | ||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 | ||||
| 
 | ||||
|     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, | ||||
|             private translate: TranslateService, private textUtils: CoreTextUtilsProvider, | ||||
|             private translate: TranslateService, private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider, | ||||
|             private lessonOfflineProvider: AddonModLessonOfflineProvider) { | ||||
|         this.logger = logger.getInstance('AddonModLessonProvider'); | ||||
| 
 | ||||
| @ -233,9 +233,9 @@ export class AddonModLessonProvider { | ||||
|         if (page.answerdata && !this.answerPageIsQuestion(page)) { | ||||
|             // It isn't a question page, but it can be an end of branch, etc. Check if the first answer has a button.
 | ||||
|             if (page.answerdata.answers && page.answerdata.answers[0]) { | ||||
|                 this.div.innerHTML = page.answerdata.answers[0][0]; | ||||
|                 const element = this.domUtils.convertToElement(page.answerdata.answers[0][0]); | ||||
| 
 | ||||
|                 return !!this.div.querySelector('input[type="button"]'); | ||||
|                 return !!element.querySelector('input[type="button"]'); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -27,8 +27,6 @@ import { AddonModQuizAccessRuleDelegate } from './access-rules-delegate'; | ||||
| @Injectable() | ||||
| export class AddonModQuizHelperProvider { | ||||
| 
 | ||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 | ||||
| 
 | ||||
|     constructor(private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, | ||||
|             private accessRuleDelegate: AddonModQuizAccessRuleDelegate, private quizProvider: AddonModQuizProvider, | ||||
|             private modalCtrl: ModalController, private quizOfflineProvider: AddonModQuizOfflineProvider) { } | ||||
| @ -153,9 +151,9 @@ export class AddonModQuizHelperProvider { | ||||
|      * @return {string}      Question's mark. | ||||
|      */ | ||||
|     getQuestionMarkFromHtml(html: string): string { | ||||
|         this.div.innerHTML = html; | ||||
|         const element = this.domUtils.convertToElement(html); | ||||
| 
 | ||||
|         return this.domUtils.getContentsOfElement(this.div, '.grade'); | ||||
|         return this.domUtils.getContentsOfElement(element, '.grade'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreFilepoolProvider } from '@providers/filepool'; | ||||
| import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| @ -56,13 +57,13 @@ export class AddonModQuizProvider { | ||||
| 
 | ||||
|     protected ROOT_CACHE_KEY = 'mmaModQuiz:'; | ||||
|     protected logger; | ||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 | ||||
| 
 | ||||
|     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, | ||||
|             private translate: TranslateService, private textUtils: CoreTextUtilsProvider, | ||||
|             private gradesHelper: CoreGradesHelperProvider, private questionDelegate: CoreQuestionDelegate, | ||||
|             private filepoolProvider: CoreFilepoolProvider, private timeUtils: CoreTimeUtilsProvider, | ||||
|             private accessRulesDelegate: AddonModQuizAccessRuleDelegate, private quizOfflineProvider: AddonModQuizOfflineProvider) { | ||||
|             private accessRulesDelegate: AddonModQuizAccessRuleDelegate, private quizOfflineProvider: AddonModQuizOfflineProvider, | ||||
|             private domUtils: CoreDomUtilsProvider) { | ||||
|         this.logger = logger.getInstance('AddonModQuizProvider'); | ||||
|     } | ||||
| 
 | ||||
| @ -1480,9 +1481,9 @@ export class AddonModQuizProvider { | ||||
|      * @return {boolean} Whether it's blocked. | ||||
|      */ | ||||
|     isQuestionBlocked(question: any): boolean { | ||||
|         this.div.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         return !!this.div.querySelector('.mod_quiz-blocked_question_warning'); | ||||
|         return !!element.querySelector('.mod_quiz-blocked_question_warning'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -14,6 +14,7 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable, Injector } from '@angular/core'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreQuestionHandler } from '@core/question/providers/delegate'; | ||||
| import { AddonQtypeNumericalHandler } from '@addon/qtype/numerical/providers/handler'; | ||||
| @ -27,7 +28,8 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | ||||
|     name = 'AddonQtypeCalculated'; | ||||
|     type = 'qtype_calculated'; | ||||
| 
 | ||||
|     constructor(private utils: CoreUtilsProvider, private numericalHandler: AddonQtypeNumericalHandler) { } | ||||
|     constructor(private utils: CoreUtilsProvider, private numericalHandler: AddonQtypeNumericalHandler, | ||||
|             private domUtils: CoreDomUtilsProvider) { } | ||||
| 
 | ||||
|     /** | ||||
|      * Return the Component to use to display the question. | ||||
| @ -120,9 +122,8 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { | ||||
|      * @return {boolean} Whether the question requires units. | ||||
|      */ | ||||
|     requiresUnits(question: any): boolean { | ||||
|         const div = document.createElement('div'); | ||||
|         div.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         return !!(div.querySelector('select[name*=unit]') || div.querySelector('input[type="radio"]')); | ||||
|         return !!(element.querySelector('select[name*=unit]') || element.querySelector('input[type="radio"]')); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -47,13 +47,12 @@ export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent | ||||
|             return this.questionHelper.showComponentError(this.onAbort); | ||||
|         } | ||||
| 
 | ||||
|         const div = document.createElement('div'); | ||||
|         div.innerHTML = this.question.html; | ||||
|         const element = this.domUtils.convertToElement(this.question.html); | ||||
| 
 | ||||
|         // Get D&D area and question text.
 | ||||
|         const ddArea = div.querySelector('.ddarea'); | ||||
|         const ddArea = element.querySelector('.ddarea'); | ||||
| 
 | ||||
|         this.question.text = this.domUtils.getContentsOfElement(div, '.qtext'); | ||||
|         this.question.text = this.domUtils.getContentsOfElement(element, '.qtext'); | ||||
|         if (!ddArea || typeof this.question.text == 'undefined') { | ||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||
| 
 | ||||
|  | ||||
| @ -47,14 +47,13 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple | ||||
|             return this.questionHelper.showComponentError(this.onAbort); | ||||
|         } | ||||
| 
 | ||||
|         const div = document.createElement('div'); | ||||
|         div.innerHTML = this.question.html; | ||||
|         const element = this.domUtils.convertToElement(this.question.html); | ||||
| 
 | ||||
|         // Get D&D area, form and question text.
 | ||||
|         const ddArea = div.querySelector('.ddarea'), | ||||
|             ddForm = div.querySelector('.ddform'); | ||||
|         const ddArea = element.querySelector('.ddarea'), | ||||
|             ddForm = element.querySelector('.ddform'); | ||||
| 
 | ||||
|         this.question.text = this.domUtils.getContentsOfElement(div, '.qtext'); | ||||
|         this.question.text = this.domUtils.getContentsOfElement(element, '.qtext'); | ||||
|         if (!ddArea || !ddForm || typeof this.question.text == 'undefined') { | ||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||
| 
 | ||||
| @ -64,7 +63,7 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple | ||||
|         // Build the D&D area HTML.
 | ||||
|         this.question.ddArea = ddArea.outerHTML; | ||||
| 
 | ||||
|         const wrongParts = div.querySelector('.wrongparts'); | ||||
|         const wrongParts = element.querySelector('.wrongparts'); | ||||
|         if (wrongParts) { | ||||
|             this.question.ddArea += wrongParts.outerHTML; | ||||
|         } | ||||
|  | ||||
| @ -47,17 +47,16 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme | ||||
|             return this.questionHelper.showComponentError(this.onAbort); | ||||
|         } | ||||
| 
 | ||||
|         const div = document.createElement('div'); | ||||
|         div.innerHTML = this.question.html; | ||||
|         const element = this.domUtils.convertToElement(this.question.html); | ||||
| 
 | ||||
|         // Replace Moodle's correct/incorrect and feedback classes with our own.
 | ||||
|         this.questionHelper.replaceCorrectnessClasses(div); | ||||
|         this.questionHelper.replaceFeedbackClasses(div); | ||||
|         this.questionHelper.replaceCorrectnessClasses(element); | ||||
|         this.questionHelper.replaceFeedbackClasses(element); | ||||
| 
 | ||||
|         // Treat the correct/incorrect icons.
 | ||||
|         this.questionHelper.treatCorrectnessIcons(div); | ||||
|         this.questionHelper.treatCorrectnessIcons(element); | ||||
| 
 | ||||
|         const answerContainer = div.querySelector('.answercontainer'); | ||||
|         const answerContainer = element.querySelector('.answercontainer'); | ||||
|         if (!answerContainer) { | ||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||
| 
 | ||||
| @ -67,7 +66,7 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme | ||||
|         this.question.readOnly = answerContainer.classList.contains('readonly'); | ||||
|         this.question.answers = answerContainer.outerHTML; | ||||
| 
 | ||||
|         this.question.text = this.domUtils.getContentsOfElement(div, '.qtext'); | ||||
|         this.question.text = this.domUtils.getContentsOfElement(element, '.qtext'); | ||||
|         if (typeof this.question.text == 'undefined') { | ||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||
| 
 | ||||
| @ -75,7 +74,7 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme | ||||
|         } | ||||
| 
 | ||||
|         // 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])')); | ||||
|         const inputEls = <HTMLElement[]> Array.from(element.querySelectorAll('input[type="hidden"]:not([name*=sequencecheck])')); | ||||
| 
 | ||||
|         inputEls.forEach((inputEl) => { | ||||
|             this.question.text += inputEl.outerHTML; | ||||
|  | ||||
| @ -33,10 +33,10 @@ export class AddonQtypeDescriptionComponent extends CoreQuestionBaseComponent im | ||||
|      * Component being initialized. | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         const questionDiv = this.initComponent(); | ||||
|         if (questionDiv) { | ||||
|         const questionEl = this.initComponent(); | ||||
|         if (questionEl) { | ||||
|             // Get the "seen" hidden input.
 | ||||
|             const input = <HTMLInputElement> questionDiv.querySelector('input[type="hidden"][name*=seen]'); | ||||
|             const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=seen]'); | ||||
|             if (input) { | ||||
|                 this.question.seenInput = { | ||||
|                     name: input.name, | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
| 
 | ||||
| import { Injectable, Injector } from '@angular/core'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreQuestionHandler } from '@core/question/providers/delegate'; | ||||
| import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; | ||||
| @ -28,10 +29,8 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | ||||
|     name = 'AddonQtypeEssay'; | ||||
|     type = 'qtype_essay'; | ||||
| 
 | ||||
|     protected div = document.createElement('div'); // A div element to search in HTML code.
 | ||||
| 
 | ||||
|     constructor(private utils: CoreUtilsProvider, private questionHelper: CoreQuestionHelperProvider, | ||||
|             private textUtils: CoreTextUtilsProvider) { } | ||||
|             private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider) { } | ||||
| 
 | ||||
|     /** | ||||
|      * Return the name of the behaviour to use for the question. | ||||
| @ -65,14 +64,14 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | ||||
|      * @return {string} Prevent submit message. Undefined or empty if can be submitted. | ||||
|      */ | ||||
|     getPreventSubmitMessage(question: any): string { | ||||
|         this.div.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         if (this.div.querySelector('div[id*=filemanager]')) { | ||||
|         if (element.querySelector('div[id*=filemanager]')) { | ||||
|             // The question allows attachments. Since the app cannot attach files yet we will prevent submitting the question.
 | ||||
|             return 'core.question.errorattachmentsnotsupported'; | ||||
|         } | ||||
| 
 | ||||
|         if (this.questionHelper.hasDraftFileUrls(this.div.innerHTML)) { | ||||
|         if (this.questionHelper.hasDraftFileUrls(element.innerHTML)) { | ||||
|             return 'core.question.errorinlinefilesnotsupported'; | ||||
|         } | ||||
|     } | ||||
| @ -85,10 +84,10 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | ||||
|      * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. | ||||
|      */ | ||||
|     isCompleteResponse(question: any, answers: any): number { | ||||
|         this.div.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         const hasInlineText = answers['answer'] && answers['answer'] !== '', | ||||
|             allowsAttachments = !!this.div.querySelector('div[id*=filemanager]'); | ||||
|             allowsAttachments = !!element.querySelector('div[id*=filemanager]'); | ||||
| 
 | ||||
|         if (!allowsAttachments) { | ||||
|             return hasInlineText ? 1 : 0; | ||||
| @ -141,10 +140,10 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { | ||||
|      * @return {void|Promise<any>} Return a promise resolved when done if async, void if sync. | ||||
|      */ | ||||
|     prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any> { | ||||
|         this.div.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         // Search the textarea to get its name.
 | ||||
|         const textarea = <HTMLTextAreaElement> this.div.querySelector('textarea[name*=_answer]'); | ||||
|         const textarea = <HTMLTextAreaElement> element.querySelector('textarea[name*=_answer]'); | ||||
| 
 | ||||
|         if (textarea && typeof answers[textarea.name] != 'undefined') { | ||||
|             // Add some HTML to the text if needed.
 | ||||
|  | ||||
| @ -824,6 +824,7 @@ body.keyboard-is-open core-ion-tabs .tabbar { | ||||
| // Fix links and videos in ion-radio and ion-checkbox. | ||||
| .item.item-radio, .item.item-checkbox { | ||||
|   .input-wrapper { | ||||
|     position: relative; | ||||
|     z-index: 5; | ||||
|     pointer-events: none; | ||||
|   } | ||||
|  | ||||
| @ -51,12 +51,12 @@ export class CoreQuestionBaseComponent { | ||||
|      */ | ||||
|     initCalculatedComponent(): void | HTMLElement { | ||||
|         // Treat the input text first.
 | ||||
|         const questionDiv = this.initInputTextComponent(); | ||||
|         if (questionDiv) { | ||||
|         const questionEl = this.initInputTextComponent(); | ||||
|         if (questionEl) { | ||||
| 
 | ||||
|             // Check if the question has a select for units.
 | ||||
|             const selectModel: any = {}, | ||||
|                 select = <HTMLSelectElement> questionDiv.querySelector('select[name*=unit]'), | ||||
|                 select = <HTMLSelectElement> questionEl.querySelector('select[name*=unit]'), | ||||
|                 options = select && Array.from(select.querySelectorAll('option')); | ||||
| 
 | ||||
|             if (select && options && options.length) { | ||||
| @ -94,24 +94,24 @@ export class CoreQuestionBaseComponent { | ||||
|                 } | ||||
| 
 | ||||
|                 // Get the accessibility label.
 | ||||
|                 const accessibilityLabel = questionDiv.querySelector('label[for="' + select.id + '"]'); | ||||
|                 const accessibilityLabel = questionEl.querySelector('label[for="' + select.id + '"]'); | ||||
|                 selectModel.accessibilityLabel = accessibilityLabel && accessibilityLabel.innerHTML; | ||||
| 
 | ||||
|                 this.question.select = selectModel; | ||||
| 
 | ||||
|                 // Check which one should be displayed first: the select or the input.
 | ||||
|                 const input = questionDiv.querySelector('input[type="text"][name*=answer]'); | ||||
|                 const input = questionEl.querySelector('input[type="text"][name*=answer]'); | ||||
|                 this.question.selectFirst = | ||||
|                         questionDiv.innerHTML.indexOf(input.outerHTML) > questionDiv.innerHTML.indexOf(select.outerHTML); | ||||
|                         questionEl.innerHTML.indexOf(input.outerHTML) > questionEl.innerHTML.indexOf(select.outerHTML); | ||||
| 
 | ||||
|                 return questionDiv; | ||||
|                 return questionEl; | ||||
|             } | ||||
| 
 | ||||
|             // Check if the question has radio buttons for units.
 | ||||
|             const radios = <HTMLInputElement[]> Array.from(questionDiv.querySelectorAll('input[type="radio"]')); | ||||
|             const radios = <HTMLInputElement[]> Array.from(questionEl.querySelectorAll('input[type="radio"]')); | ||||
|             if (!radios.length) { | ||||
|                 // No select and no radio buttons. The units need to be entered in the input text.
 | ||||
|                 return questionDiv; | ||||
|                 return questionEl; | ||||
|             } | ||||
| 
 | ||||
|             this.question.options = []; | ||||
| @ -126,7 +126,7 @@ export class CoreQuestionBaseComponent { | ||||
|                         disabled: radioEl.disabled | ||||
|                     }, | ||||
|                     // Get the label with the question text.
 | ||||
|                     label = <HTMLElement> questionDiv.querySelector('label[for="' + option.id + '"]'); | ||||
|                     label = <HTMLElement> questionEl.querySelector('label[for="' + option.id + '"]'); | ||||
| 
 | ||||
|                 this.question.optionsName = option.name; | ||||
| 
 | ||||
| @ -154,9 +154,9 @@ export class CoreQuestionBaseComponent { | ||||
|             } | ||||
| 
 | ||||
|             // Check which one should be displayed first: the options or the input.
 | ||||
|             const input = questionDiv.querySelector('input[type="text"][name*=answer]'); | ||||
|             const input = questionEl.querySelector('input[type="text"][name*=answer]'); | ||||
|             this.question.optionsFirst = | ||||
|                     questionDiv.innerHTML.indexOf(input.outerHTML) > questionDiv.innerHTML.indexOf(radios[0].outerHTML); | ||||
|                     questionEl.innerHTML.indexOf(input.outerHTML) > questionEl.innerHTML.indexOf(radios[0].outerHTML); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -172,18 +172,17 @@ export class CoreQuestionBaseComponent { | ||||
|             return this.questionHelper.showComponentError(this.onAbort); | ||||
|         } | ||||
| 
 | ||||
|         const div = document.createElement('div'); | ||||
|         div.innerHTML = this.question.html; | ||||
|         const element = this.domUtils.convertToElement(this.question.html); | ||||
| 
 | ||||
|         // Extract question text.
 | ||||
|         this.question.text = this.domUtils.getContentsOfElement(div, '.qtext'); | ||||
|         this.question.text = this.domUtils.getContentsOfElement(element, '.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); | ||||
|         } | ||||
| 
 | ||||
|         return div; | ||||
|         return element; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -192,24 +191,24 @@ export class CoreQuestionBaseComponent { | ||||
|      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. | ||||
|      */ | ||||
|     initEssayComponent(): void | HTMLElement { | ||||
|         const questionDiv = this.initComponent(); | ||||
|         const questionEl = this.initComponent(); | ||||
| 
 | ||||
|         if (questionDiv) { | ||||
|         if (questionEl) { | ||||
|             // First search the textarea.
 | ||||
|             const textarea = <HTMLTextAreaElement> questionDiv.querySelector('textarea[name*=_answer]'); | ||||
|             this.question.allowsAttachments = !!questionDiv.querySelector('div[id*=filemanager]'); | ||||
|             this.question.isMonospaced = !!questionDiv.querySelector('.qtype_essay_monospaced'); | ||||
|             this.question.isPlainText = this.question.isMonospaced || !!questionDiv.querySelector('.qtype_essay_plain'); | ||||
|             this.question.hasDraftFiles = this.questionHelper.hasDraftFileUrls(questionDiv.innerHTML); | ||||
|             const textarea = <HTMLTextAreaElement> questionEl.querySelector('textarea[name*=_answer]'); | ||||
|             this.question.allowsAttachments = !!questionEl.querySelector('div[id*=filemanager]'); | ||||
|             this.question.isMonospaced = !!questionEl.querySelector('.qtype_essay_monospaced'); | ||||
|             this.question.isPlainText = this.question.isMonospaced || !!questionEl.querySelector('.qtype_essay_plain'); | ||||
|             this.question.hasDraftFiles = this.questionHelper.hasDraftFileUrls(questionEl.innerHTML); | ||||
| 
 | ||||
|             if (!textarea) { | ||||
|                 // Textarea not found, we might be in review. Search the answer and the attachments.
 | ||||
|                 this.question.answer = this.domUtils.getContentsOfElement(questionDiv, '.qtype_essay_response'); | ||||
|                 this.question.answer = this.domUtils.getContentsOfElement(questionEl, '.qtype_essay_response'); | ||||
|                 this.question.attachments = this.questionHelper.getQuestionAttachmentsFromHtml( | ||||
|                         this.domUtils.getContentsOfElement(questionDiv, '.attachments')); | ||||
|                         this.domUtils.getContentsOfElement(questionEl, '.attachments')); | ||||
|             } else { | ||||
|                 // Textarea found.
 | ||||
|                 const input = <HTMLInputElement> questionDiv.querySelector('input[type="hidden"][name*=answerformat]'), | ||||
|                 const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=answerformat]'), | ||||
|                     content = textarea.innerHTML; | ||||
| 
 | ||||
|                 this.question.textarea = { | ||||
| @ -241,11 +240,10 @@ export class CoreQuestionBaseComponent { | ||||
|             return this.questionHelper.showComponentError(this.onAbort); | ||||
|         } | ||||
| 
 | ||||
|         const div = document.createElement('div'); | ||||
|         div.innerHTML = this.question.html; | ||||
|         const element = this.domUtils.convertToElement(this.question.html); | ||||
| 
 | ||||
|         // Get question content.
 | ||||
|         const content = <HTMLElement> div.querySelector(contentSelector); | ||||
|         const content = <HTMLElement> element.querySelector(contentSelector); | ||||
|         if (!content) { | ||||
|             this.logger.warn('Aborting because of an error parsing question.', this.question.name); | ||||
| 
 | ||||
| @ -257,11 +255,11 @@ export class CoreQuestionBaseComponent { | ||||
|         this.domUtils.removeElement(content, '.validationerror'); | ||||
| 
 | ||||
|         // Replace Moodle's correct/incorrect and feedback classes with our own.
 | ||||
|         this.questionHelper.replaceCorrectnessClasses(div); | ||||
|         this.questionHelper.replaceFeedbackClasses(div); | ||||
|         this.questionHelper.replaceCorrectnessClasses(element); | ||||
|         this.questionHelper.replaceFeedbackClasses(element); | ||||
| 
 | ||||
|         // Treat the correct/incorrect icons.
 | ||||
|         this.questionHelper.treatCorrectnessIcons(div); | ||||
|         this.questionHelper.treatCorrectnessIcons(element); | ||||
| 
 | ||||
|         // Set the question text.
 | ||||
|         this.question.text = content.innerHTML; | ||||
| @ -273,10 +271,10 @@ export class CoreQuestionBaseComponent { | ||||
|      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. | ||||
|      */ | ||||
|     initInputTextComponent(): void | HTMLElement { | ||||
|         const questionDiv = this.initComponent(); | ||||
|         if (questionDiv) { | ||||
|         const questionEl = this.initComponent(); | ||||
|         if (questionEl) { | ||||
|             // Get the input element.
 | ||||
|             const input = <HTMLInputElement> questionDiv.querySelector('input[type="text"][name*=answer]'); | ||||
|             const input = <HTMLInputElement> questionEl.querySelector('input[type="text"][name*=answer]'); | ||||
|             if (!input) { | ||||
|                 this.logger.warn('Aborting because couldn\'t find input.', this.question.name); | ||||
| 
 | ||||
| @ -302,7 +300,7 @@ export class CoreQuestionBaseComponent { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return questionDiv; | ||||
|         return questionEl; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -311,11 +309,11 @@ export class CoreQuestionBaseComponent { | ||||
|      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. | ||||
|      */ | ||||
|     initMatchComponent(): void | HTMLElement { | ||||
|         const questionDiv = this.initComponent(); | ||||
|         const questionEl = this.initComponent(); | ||||
| 
 | ||||
|         if (questionDiv) { | ||||
|         if (questionEl) { | ||||
|             // Find rows.
 | ||||
|             const rows = Array.from(questionDiv.querySelectorAll('table.answer tr')); | ||||
|             const rows = Array.from(questionEl.querySelectorAll('table.answer tr')); | ||||
|             if (!rows || !rows.length) { | ||||
|                 this.logger.warn('Aborting because couldn\'t find any row.', this.question.name); | ||||
| 
 | ||||
| @ -394,7 +392,7 @@ export class CoreQuestionBaseComponent { | ||||
|             this.question.loaded = true; | ||||
|         } | ||||
| 
 | ||||
|         return questionDiv; | ||||
|         return questionEl; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -403,19 +401,19 @@ export class CoreQuestionBaseComponent { | ||||
|      * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. | ||||
|      */ | ||||
|     initMultichoiceComponent(): void | HTMLElement { | ||||
|         const questionDiv = this.initComponent(); | ||||
|         const questionEl = this.initComponent(); | ||||
| 
 | ||||
|         if (questionDiv) { | ||||
|         if (questionEl) { | ||||
| 
 | ||||
|             // Get the prompt.
 | ||||
|             this.question.prompt = this.domUtils.getContentsOfElement(questionDiv, '.prompt'); | ||||
|             this.question.prompt = this.domUtils.getContentsOfElement(questionEl, '.prompt'); | ||||
| 
 | ||||
|             // Search radio buttons first (single choice).
 | ||||
|             let options = <HTMLInputElement[]> Array.from(questionDiv.querySelectorAll('input[type="radio"]')); | ||||
|             let options = <HTMLInputElement[]> Array.from(questionEl.querySelectorAll('input[type="radio"]')); | ||||
|             if (!options || !options.length) { | ||||
|                 // Radio buttons not found, it should be a multi answer. Search for checkbox.
 | ||||
|                 this.question.multi = true; | ||||
|                 options = <HTMLInputElement[]> Array.from(questionDiv.querySelectorAll('input[type="checkbox"]')); | ||||
|                 options = <HTMLInputElement[]> Array.from(questionEl.querySelectorAll('input[type="checkbox"]')); | ||||
| 
 | ||||
|                 if (!options || !options.length) { | ||||
|                     // No checkbox found either. Abort.
 | ||||
| @ -441,7 +439,7 @@ export class CoreQuestionBaseComponent { | ||||
|                 this.question.optionsName = option.name; | ||||
| 
 | ||||
|                 // Get the label with the question text.
 | ||||
|                 const label = questionDiv.querySelector('label[for="' + option.id + '"]'); | ||||
|                 const label = questionEl.querySelector('label[for="' + option.id + '"]'); | ||||
|                 if (label) { | ||||
|                     option.text = label.innerHTML; | ||||
| 
 | ||||
| @ -483,6 +481,6 @@ export class CoreQuestionBaseComponent { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return questionDiv; | ||||
|         return questionEl; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -29,7 +29,6 @@ import { CoreQuestionDelegate } from './delegate'; | ||||
| @Injectable() | ||||
| export class CoreQuestionHelperProvider { | ||||
|     protected lastErrorShown = 0; | ||||
|     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, | ||||
| @ -70,15 +69,15 @@ export class CoreQuestionHelperProvider { | ||||
|     extractQbehaviourButtons(question: any, selector?: string): void { | ||||
|         selector = selector || '.im-controls input[type="submit"]'; | ||||
| 
 | ||||
|         this.div.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         // Search the buttons.
 | ||||
|         const buttons = <HTMLInputElement[]> Array.from(this.div.querySelectorAll(selector)); | ||||
|         const buttons = <HTMLInputElement[]> Array.from(element.querySelectorAll(selector)); | ||||
|         buttons.forEach((button) => { | ||||
|             this.addBehaviourButton(question, button); | ||||
|         }); | ||||
| 
 | ||||
|         question.html = this.div.innerHTML; | ||||
|         question.html = element.innerHTML; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -91,9 +90,9 @@ export class CoreQuestionHelperProvider { | ||||
|      * @return {boolean} Wether the certainty is found. | ||||
|      */ | ||||
|     extractQbehaviourCBM(question: any): boolean { | ||||
|         this.div.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         const labels = Array.from(this.div.querySelectorAll('.im-controls .certaintychoices label[for*="certainty"]')); | ||||
|         const labels = Array.from(element.querySelectorAll('.im-controls .certaintychoices label[for*="certainty"]')); | ||||
|         question.behaviourCertaintyOptions = []; | ||||
| 
 | ||||
|         labels.forEach((label) => { | ||||
| @ -158,10 +157,10 @@ export class CoreQuestionHelperProvider { | ||||
|      * @return {boolean} Whether the seen input is found. | ||||
|      */ | ||||
|     extractQbehaviourSeenInput(question: any): boolean { | ||||
|         this.div.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         // Search the "seen" input.
 | ||||
|         const seenInput = <HTMLInputElement> this.div.querySelector('input[type="hidden"][name*=seen]'); | ||||
|         const seenInput = <HTMLInputElement> element.querySelector('input[type="hidden"][name*=seen]'); | ||||
|         if (seenInput) { | ||||
|             // Get the data and remove the input.
 | ||||
|             question.behaviourSeenInput = { | ||||
| @ -169,7 +168,7 @@ export class CoreQuestionHelperProvider { | ||||
|                 value: seenInput.value | ||||
|             }; | ||||
|             seenInput.parentElement.removeChild(seenInput); | ||||
|             question.html = this.div.innerHTML; | ||||
|             question.html = element.innerHTML; | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
| @ -214,9 +213,9 @@ export class CoreQuestionHelperProvider { | ||||
|      * @param {string} attrName Name of the attribute to store the HTML in. | ||||
|      */ | ||||
|     protected extractQuestionLastElementNotInContent(question: any, selector: string, attrName: string): void { | ||||
|         this.div.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement(question.html); | ||||
| 
 | ||||
|         const matches = <HTMLElement[]> Array.from(this.div.querySelectorAll(selector)); | ||||
|         const matches = <HTMLElement[]> Array.from(element.querySelectorAll(selector)); | ||||
| 
 | ||||
|         // Get the last element and check it's not in the question contents.
 | ||||
|         let last = matches.pop(); | ||||
| @ -225,7 +224,7 @@ export class CoreQuestionHelperProvider { | ||||
|                 // Not in question contents. Add it to a separate attribute and remove it from the HTML.
 | ||||
|                 question[attrName] = last.innerHTML; | ||||
|                 last.parentElement.removeChild(last); | ||||
|                 question.html = this.div.innerHTML; | ||||
|                 question.html = element.innerHTML; | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| @ -283,11 +282,10 @@ export class CoreQuestionHelperProvider { | ||||
|      * @return {any} Object where the keys are the names. | ||||
|      */ | ||||
|     getAllInputNamesFromHtml(html: string): any { | ||||
|         const form = document.createElement('form'), | ||||
|         const element = this.domUtils.convertToElement('<form>' + html + '</form>'), | ||||
|             form = <HTMLFormElement> element.children[0], | ||||
|             answers = {}; | ||||
| 
 | ||||
|         form.innerHTML = html; | ||||
| 
 | ||||
|         // Search all input elements.
 | ||||
|         Array.from(form.elements).forEach((element: HTMLInputElement) => { | ||||
|             const name = element.name || ''; | ||||
| @ -350,13 +348,13 @@ export class CoreQuestionHelperProvider { | ||||
|      * @return {Object[]}    Attachments. | ||||
|      */ | ||||
|     getQuestionAttachmentsFromHtml(html: string): any[] { | ||||
|         this.div.innerHTML = html; | ||||
|         const element = this.domUtils.convertToElement(html); | ||||
| 
 | ||||
|         // Remove the filemanager (area to attach files to a question).
 | ||||
|         this.domUtils.removeElement(this.div, 'div[id*=filemanager]'); | ||||
|         this.domUtils.removeElement(element, 'div[id*=filemanager]'); | ||||
| 
 | ||||
|         // Search the anchors.
 | ||||
|         const anchors = Array.from(this.div.querySelectorAll('a')), | ||||
|         const anchors = Array.from(element.querySelectorAll('a')), | ||||
|             attachments = []; | ||||
| 
 | ||||
|         anchors.forEach((anchor) => { | ||||
| @ -383,10 +381,10 @@ export class CoreQuestionHelperProvider { | ||||
|      */ | ||||
|     getQuestionSequenceCheckFromHtml(html: string): {name: string, value: string} { | ||||
|         if (html) { | ||||
|             this.div.innerHTML = html; | ||||
|             const element = this.domUtils.convertToElement(html); | ||||
| 
 | ||||
|             // Search the input holding the sequencecheck.
 | ||||
|             const input = <HTMLInputElement> this.div.querySelector('input[name*=sequencecheck]'); | ||||
|             const input = <HTMLInputElement> element.querySelector('input[name*=sequencecheck]'); | ||||
|             if (input && typeof input.name != 'undefined' && typeof input.value != 'undefined') { | ||||
|                 return { | ||||
|                     name: input.name, | ||||
| @ -415,9 +413,9 @@ export class CoreQuestionHelperProvider { | ||||
|      * @return {string} Validation error message if present. | ||||
|      */ | ||||
|     getValidationErrorFromHtml(html: string): string { | ||||
|         this.div.innerHTML = html; | ||||
|         const element = this.domUtils.convertToElement(html); | ||||
| 
 | ||||
|         return this.domUtils.getContentsOfElement(this.div, '.validationerror'); | ||||
|         return this.domUtils.getContentsOfElement(element, '.validationerror'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -443,8 +441,8 @@ export class CoreQuestionHelperProvider { | ||||
|      * @param {any} question Question. | ||||
|      */ | ||||
|     loadLocalAnswersInHtml(question: any): void { | ||||
|         const form = document.createElement('form'); | ||||
|         form.innerHTML = question.html; | ||||
|         const element = this.domUtils.convertToElement('<form>' + question.html + '</form>'), | ||||
|             form = <HTMLFormElement> element.children[0]; | ||||
| 
 | ||||
|         // Search all input elements.
 | ||||
|         Array.from(form.elements).forEach((element: HTMLInputElement | HTMLButtonElement) => { | ||||
| @ -574,9 +572,9 @@ export class CoreQuestionHelperProvider { | ||||
|      * @return {boolean} Whether the button is found. | ||||
|      */ | ||||
|     protected searchBehaviourButton(question: any, htmlProperty: string, selector: string): boolean { | ||||
|         this.div.innerHTML = question[htmlProperty]; | ||||
|         const element = this.domUtils.convertToElement(question[htmlProperty]); | ||||
| 
 | ||||
|         const button = <HTMLInputElement> this.div.querySelector(selector); | ||||
|         const button = <HTMLInputElement> element.querySelector(selector); | ||||
|         if (button) { | ||||
|             // Add a behaviour button to the question's "behaviourButtons" property.
 | ||||
|             this.addBehaviourButton(question, button); | ||||
| @ -585,7 +583,7 @@ export class CoreQuestionHelperProvider { | ||||
|             button.parentElement.removeChild(button); | ||||
| 
 | ||||
|             // Update the question's html.
 | ||||
|             question[htmlProperty] = this.div.innerHTML; | ||||
|             question[htmlProperty] = element.innerHTML; | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
|   <meta charset="UTF-8"> | ||||
|   <title>Moodle Desktop</title> <!-- Title is used in desktop, but should be ignored in mobile apps. --> | ||||
|   <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> | ||||
|   <meta http-equiv="Content-Security-Policy" content="default-src * filesystem: cdvfile: file: gap: https://ssl.gstatic.com; img-src * filesystem: gap: data: cdvfile: file: https://ssl.gstatic.com android-webview-video-poster: blob:; style-src 'self' 'unsafe-inline' filesystem: cdvfile: file:; script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:* filesystem: cdvfile: file:; media-src * filesystem: cdvfile: file: gap: blob:"> | ||||
|   <meta http-equiv="Content-Security-Policy" content="default-src * filesystem: cdvfile: file: data: gap: https://ssl.gstatic.com; img-src * filesystem: gap: data: cdvfile: file: https://ssl.gstatic.com android-webview-video-poster: blob:; style-src 'self' 'unsafe-inline' filesystem: cdvfile: file:; script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:* filesystem: cdvfile: file:; media-src * filesystem: cdvfile: file: gap: blob:"> | ||||
|   <meta name="format-detection" content="telephone=no"> | ||||
|   <meta name="msapplication-tap-highlight" content="no"> | ||||
| 
 | ||||
|  | ||||
| @ -35,7 +35,8 @@ export class CoreDomUtilsProvider { | ||||
|         'search', 'tel', 'text', 'time', 'url', 'week']; | ||||
|     protected INSTANCE_ID_ATTR_NAME = 'core-instance-id'; | ||||
| 
 | ||||
|     protected element = document.createElement('div'); // Fake element to use in some functions, to prevent creating it each time.
 | ||||
|     protected parser = new DOMParser(); // Parser to treat HTML.
 | ||||
| 
 | ||||
|     protected matchesFn: string; // Name of the "matches" function to use when simulating a closest call.
 | ||||
|     protected instances: {[id: string]: any} = {}; // Store component/directive instances by id.
 | ||||
|     protected lastInstanceId = 0; | ||||
| @ -124,6 +125,28 @@ export class CoreDomUtilsProvider { | ||||
|         return Promise.resolve(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convert some HTML as text into an HTMLElement. This HTML is put inside a div or a body. | ||||
|      * | ||||
|      * @param {string} html Text to convert. | ||||
|      * @return {HTMLElement} Element. | ||||
|      */ | ||||
|     convertToElement(html: string): HTMLElement { | ||||
|         if (this.parser) { | ||||
|             const doc = this.parser.parseFromString(html, 'text/html'); | ||||
| 
 | ||||
|             // Verify that the doc is valid. In some OS like Android 4.4 only XML parsing is supported, so doc is null.
 | ||||
|             if (doc) { | ||||
|                 return doc.body; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const element = document.createElement('div'); | ||||
|         element.innerHTML = html; | ||||
| 
 | ||||
|         return element; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a "cancelled" error. These errors won't display an error message in showErrorModal functions. | ||||
|      * | ||||
| @ -169,8 +192,8 @@ export class CoreDomUtilsProvider { | ||||
|         const urls = []; | ||||
|         let elements; | ||||
| 
 | ||||
|         this.element.innerHTML = html; | ||||
|         elements = this.element.querySelectorAll('a, img, audio, video, source, track'); | ||||
|         const element = this.convertToElement(html); | ||||
|         elements = element.querySelectorAll('a, img, audio, video, source, track'); | ||||
| 
 | ||||
|         for (let i = 0; i < elements.length; i++) { | ||||
|             const element = elements[i]; | ||||
| @ -599,21 +622,21 @@ export class CoreDomUtilsProvider { | ||||
|     removeElementFromHtml(html: string, selector: string, removeAll?: boolean): string { | ||||
|         let selected; | ||||
| 
 | ||||
|         this.element.innerHTML = html; | ||||
|         const element = this.convertToElement(html); | ||||
| 
 | ||||
|         if (removeAll) { | ||||
|             selected = this.element.querySelectorAll(selector); | ||||
|             selected = element.querySelectorAll(selector); | ||||
|             for (let i = 0; i < selected.length; i++) { | ||||
|                 selected[i].remove(); | ||||
|             } | ||||
|         } else { | ||||
|             selected = this.element.querySelector(selector); | ||||
|             selected = element.querySelector(selector); | ||||
|             if (selected) { | ||||
|                 selected.remove(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return this.element.innerHTML; | ||||
|         return element.innerHTML; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -665,10 +688,10 @@ export class CoreDomUtilsProvider { | ||||
|         let media, | ||||
|             anchors; | ||||
| 
 | ||||
|         this.element.innerHTML = html; | ||||
|         const element = this.convertToElement(html); | ||||
| 
 | ||||
|         // Treat elements with src (img, audio, video, ...).
 | ||||
|         media = this.element.querySelectorAll('img, video, audio, source, track'); | ||||
|         media = element.querySelectorAll('img, video, audio, source, track'); | ||||
|         media.forEach((media: HTMLElement) => { | ||||
|             let newSrc = paths[this.textUtils.decodeURIComponent(media.getAttribute('src'))]; | ||||
| 
 | ||||
| @ -686,7 +709,7 @@ export class CoreDomUtilsProvider { | ||||
|         }); | ||||
| 
 | ||||
|         // Now treat links.
 | ||||
|         anchors = this.element.querySelectorAll('a'); | ||||
|         anchors = element.querySelectorAll('a'); | ||||
|         anchors.forEach((anchor: HTMLElement) => { | ||||
|             const href = this.textUtils.decodeURIComponent(anchor.getAttribute('href')), | ||||
|                 newUrl = paths[href]; | ||||
| @ -700,7 +723,7 @@ export class CoreDomUtilsProvider { | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         return this.element.innerHTML; | ||||
|         return element.innerHTML; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1104,13 +1127,13 @@ export class CoreDomUtilsProvider { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Converts HTML formatted text to DOM element. | ||||
|      * @param  {string}      text HTML text. | ||||
|      * @return {HTMLCollection}      Same text converted to HTMLCollection. | ||||
|      * Converts HTML formatted text to DOM element(s). | ||||
|      * | ||||
|      * @param {string} text HTML text. | ||||
|      * @return {HTMLCollection} Same text converted to HTMLCollection. | ||||
|      */ | ||||
|     toDom(text: string): HTMLCollection { | ||||
|         const element = document.createElement('div'); | ||||
|         element.innerHTML = text; | ||||
|         const element = this.convertToElement(text); | ||||
| 
 | ||||
|         return element.children; | ||||
|     } | ||||
|  | ||||
| @ -68,7 +68,7 @@ export class CoreTextUtilsProvider { | ||||
|         {old: /_mmaModWorkshop/g, new: '_AddonModWorkshop'}, | ||||
|     ]; | ||||
| 
 | ||||
|     protected element = document.createElement('div'); // Fake element to use in some functions, to prevent creating it each time.
 | ||||
|     protected parser = new DOMParser(); // Parser to treat HTML.
 | ||||
| 
 | ||||
|     constructor(private translate: TranslateService, private langProvider: CoreLangProvider, private modalCtrl: ModalController) { } | ||||
| 
 | ||||
| @ -142,8 +142,8 @@ export class CoreTextUtilsProvider { | ||||
|         // First, we use a regexpr.
 | ||||
|         text = text.replace(/(<([^>]+)>)/ig, ''); | ||||
|         // Then, we rely on the browser. We need to wrap the text to be sure is HTML.
 | ||||
|         this.element.innerHTML = text; | ||||
|         text = this.element.textContent; | ||||
|         const element = this.convertToElement(text); | ||||
|         text = element.textContent; | ||||
|         // Recover or remove new lines.
 | ||||
|         text = this.replaceNewLines(text, singleLine ? ' ' : '<br>'); | ||||
| 
 | ||||
| @ -176,6 +176,29 @@ export class CoreTextUtilsProvider { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convert some HTML as text into an HTMLElement. This HTML is put inside a div or a body. | ||||
|      * This function is the same as in DomUtils, but we cannot use that one because of circular dependencies. | ||||
|      * | ||||
|      * @param {string} html Text to convert. | ||||
|      * @return {HTMLElement} Element. | ||||
|      */ | ||||
|     protected convertToElement(html: string): HTMLElement { | ||||
|         if (this.parser) { | ||||
|             const doc = this.parser.parseFromString(html, 'text/html'); | ||||
| 
 | ||||
|             // Verify that the doc is valid. In some OS like Android 4.4 only XML parsing is supported, so doc is null.
 | ||||
|             if (doc) { | ||||
|                 return doc.body; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const element = document.createElement('div'); | ||||
|         element.innerHTML = html; | ||||
| 
 | ||||
|         return element; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Count words in a text. | ||||
|      * | ||||
| @ -225,9 +248,8 @@ export class CoreTextUtilsProvider { | ||||
|      */ | ||||
|     decodeHTMLEntities(text: string): string { | ||||
|         if (text) { | ||||
|             this.element.innerHTML = text; | ||||
|             text = this.element.textContent; | ||||
|             this.element.textContent = ''; | ||||
|             const element = this.convertToElement(text); | ||||
|             text = element.textContent; | ||||
|         } | ||||
| 
 | ||||
|         return text; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user